3.13 Границы ошибок

Доступны с 16 версии.


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



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


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

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


Замечание.

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

Компонент-класс становится границей ошибки, если он определяет один из (или оба) методов жизненного цикла: static getDerivedStateFromError() или componentDidCatch (error, info). Используйте static getDerivedStateFromError() для отрисовки аварийного интерфейса после возникновения ошибки. Используйте componentDidCatch (error, info) для регистрации информации об ошибке.


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

    static getDerivedStateFromError(error) {
      // Обновление состояния, чтобы при последующей отрисовке показать аварийный UI.
      return { hasError: true };
    }

    componentDidCatch(error, info) {
      // Вы можете прологировать ошибку с помощью сервиса отчета об ошибках
      logErrorToMyService(error, info);
    }

    render() {
      if (this.state.hasError) {
        // Вы можете отрисовать любой резервный UI
        return <h1>Возникли ошибки.</h1>;
      }
      return this.props.children;
    }
  }
  

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


Код
    
  <ErrorBoundary>
    <MyWidget />
  </ErrorBoundary>
  

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

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



3.13.2 Демо


Ознакомьтесь с данным примером объявления и использования границы ошибок с React 16.



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

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



3.13.4 Новое поведение для необрабатываемых ошибок


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

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



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

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

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



3.13.5 Трассировка стека компонентов


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

Вы также можете увидеть имена файлов и номера строк в трассировке стека компонентов. Это работает по умолчанию в проектах Create React App :

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


Замечание.

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



3.13.6 Как насчёт try/catch?


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


Код
    
  try {
    showButton();
  } catch (error) {
    // ...
  }
  

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


Код
    
  <Button />
  

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



3.13.7 Как насчет обработчиков событий?


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

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

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


Код
    
  class ErrorCapturer extends React.Component {
    constructor(props) {
      super(props);
      this.state = { error: null };
    }
    
    onClick = () => {
      try {
        // Выполните что-то, что выбросит ошибку
      } catch (error) {
        this.setState({ error });
      }
    }
  
    render() {
      if (this.state.error) {
        return <h1>Ошибка перехвачена!.</h1>
      }
      return <div onClick={this.onClick}>Нажать</div>
    }
  }
  

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



3.13.8 Изменения наименований в сравнении с React 15


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

Для этого изменения мы предоставили модификатор кода для автоматической миграции.