В прошлом ошибки JavaScript внутри компонентов, используемых для искажения внутреннего состояния React и вызывали его испускание критических ошибок на следующих рендерах. Эти ошибки всегда вызывались более ранней ошибкой в ​​коде приложения, но React не предоставлял способ грамотно обрабатывать их в компонентах и ​​не мог восстановить их.

Знакомство с границами ошибок

Ошибка JavaScript в части пользовательского интерфейса не должна разрушать все приложение. Чтобы решить эту проблему для пользователей React, React 16 представляет новую концепцию «границы ошибки».

Границы ошибок - это компоненты React, которые улавливают ошибки JavaScript в любом месте их дочернего дерева компонентов, регистрируют эти ошибки и отображают резервный интерфейс вместо разбитого дерева компонентов. Границы ошибок ломают ошибки при рендеринге, в методах жизненного цикла и в конструкторах всего дерева под ними.

Заметка

Границы ошибок не допускают ошибок для:

Обработчики событий
Асинхронный код (например , setTimeoutили requestAnimationFrame обратные вызовы)
Отправка серверной части
Ошибки, сброшенные на самой границе ошибки (а не на ее дочерние элементы)

Компонент класса становится границей ошибки, если он определяет новый метод жизненного цикла componentDidCatch(error, info):

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  componentDidCatch(error, info) {
    // Display fallback UI
    this.setState({ hasError: true });
    // You can also log the error to an error reporting service
    logErrorToMyService(error, info);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

Затем вы можете использовать его как обычный компонент:

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

componentDidCatch() метод работает как JavaScript catch {} блок, но для компоненто. Только компоненты класса могут быть границами ошибок. На практике большую часть времени вы захотите объявить компонент границы ошибки один раз и использовать его во всем приложении.

Обратите внимание, что границы ошибок только ломают ошибки в компонентах ниже их в дереве . Граница ошибки не может поймать ошибку внутри себя. Если граница ошибки не пытается отобразить сообщение об ошибке, ошибка будет распространяться на ближайшую границу ошибки над ней. Это тоже похоже на то, как блок catch {} работает в JavaScript.

Параметры компонентаDidCatch

error это ошибка, которая была выбрана.

infoэто объект с componentStack ключом. Свойство содержит информацию о стеке компонентов во время сброшенной ошибки.

//...
componentDidCatch(error, info) {
  
  /* Example stack information:
     in ComponentThatThrows (created by App)
     in ErrorBoundary (created by App)
     in div (created by App)
     in App
  */
  logComponentStackToMyService(info.componentStack);
}

//...

Где установить границы ошибок

Гранулярность границ ошибок зависит от вас. Вы можете обернуть компоненты маршрута верхнего уровня, чтобы отобразить сообщение «Что-то пошло не так» для пользователя, так же как серверные фреймворки часто обрабатывают сбои. Вы также можете обернуть отдельные виджеты на границе ошибок, чтобы защитить их от сбоя остальной части приложения.

Новое поведение для ошибок, которые не были обнаружены

Это изменение имеет важное значение. Начиная с React 16, ошибки, которые не были захвачены какой-либо границей ошибок, приведут к размонтированию всего дерева компонентов React.

Хуже оставить поврежденный пользовательский интерфейс на месте, чем полностью удалить его. Например, в таком продукте, как Messenger, оставляющий видимый сломанный пользовательский интерфейс, может привести к тому, что кто-то отправит сообщение не тому человеку. Аналогично, для приложения платежей хуже отображать неправильную сумму, чем ничего не делать.

Это изменение означает, что по мере перехода на React 16 вы, скорее всего, обнаружите существующие сбои в своем приложении, которые были незаметны раньше. Добавление границ ошибок позволяет обеспечить лучший пользовательский интерфейс, когда что-то пойдет не так.

Например, Facebook Messenger обертывает содержимое боковой панели, информационной панели, журнала беседы и сообщения, вводимого в отдельные границы ошибок. Если какой-то компонент в одной из этих областей пользовательского интерфейса падает, остальные остаются интерактивными.

Мы также рекомендуем вам использовать службы отчетов об ошибках JS (или создать свои собственные), чтобы вы могли узнать о необработанных исключениях, которые происходят в процессе производства, и исправить их.

Следы компонентов

React 16 печатает все ошибки, возникшие при рендеринге консоли в процессе разработки, даже если приложение случайно проглатывает их. Помимо сообщения об ошибке и стека JavaScript, он также обеспечивает отслеживание стека компонентов. Вы также можете увидеть имена файлов и номера строк в трассировке стека компонентов. 

Если вы не используете приложение Create React, вы можете добавить этот плагин вручную в свою конфигурацию Babel. Обратите внимание, что он предназначен только для разработки и должен быть отключен в процессе производства.

Заметка

Имена компонентов, отображаемые в трассировке стека, зависят от Function.name свойства. Если вы поддерживаете более старые браузеры и устройства, которые еще не могут обеспечить это изначально (например, IE 11), подумайте о включении Function.nameполифайла в вашем комплекте приложений, например function.name-polyfill. Кроме того, вы можете явно указать displayName свойство для всех своих компонентов.

Как насчет try/catch?

try/catch отлично, но он работает только для императивного кода:

try {
  showButton();
} catch (error) {
  // ...
}

Однако компоненты React являются декларативными и указывают, что нужно сделать:

<Button />

Границы ошибок сохраняют декларативный характер React и ведут себя так, как вы ожидали. Например, даже если в componentDidUpdate возникает ошибка, вызванная setState где-то глубоко в дереве, она все равно будет правильно распространяться на ближайшую границу ошибки.

Как о обработчиках событий?

Границы ошибок не ловят ошибки внутри обработчиков событий.

React не нуждается в границах ошибок, чтобы восстановить ошибки в обработчиках событий. В отличие от методов рендеринга и привязок жизненного цикла, обработчики событий не выполняются во время рендеринга. Поэтому, если они бросают, React все еще знает, что отображать на экране.

Если вам нужно поймать ошибку внутри обработчика событий, используйте обычный JavaScript try/catchstatement:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  
  handleClick = () => {
    try {
      // Do something that could throw
    } catch (error) {
      this.setState({ error });
    }
  }

  render() {
    if (this.state.error) {
      return <h1>Caught an error.</h1>
    }
    return <div onClick={this.handleClick}>Click Me</div>
  }
}

Обратите внимание, что приведенный выше пример демонстрирует регулярное поведение JavaScript и не использует границы ошибок.