Обработка ошибок в React 16

26 Июля, 2017. Dan Abramov(Дэн Абрамов)

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

Поведение в React 15 и ранее

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

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

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

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

Компонент класса становится границей ошибки, если он определяет новый метод жизненного цикла, называемый 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>
  

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Почему не try/catch?

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


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

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


Код
    
  <Button />
  

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

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

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

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