Рекомендации по обработке и регистрации ошибок в React

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


Лучшие методы обработки ошибок

Надежная обработка ошибок необходима для нормальной работы приложений. Вот несколько подходящих методик.

1. Обработка ошибок в компонентах класса с помощью граничного метода

Error Boundaries (граничный метод)  —  это самый простой и эффективный способ обработки ошибок в компонентах React.

В этом случае можно создать компонент границ ошибки, включив метод жизненного цикла componentDidCatch (errorinfo). Лучше всего использовать эту функцию для регистрации информации об ошибках в сервисе регистрации ошибок (logging service).

Кроме того, можно использовать статическую функцию getDerivedStateFromError(error) для обновления состояния и метод render() для отображения резервного пользовательского интерфейса (fallback UI) в случае возникновения ошибки.

Пример компонента границ ошибки.

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

static getDerivedStateFromError(error) {
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// Зарегистрируйте ошибку в сервисе отчета об ошибках
errorService.log({ error, errorInfo });
}

render() {
if (this.state.hasError) {
return <h1>Oops, something went wrong.</h1>;
}
return this.props.children;
}
}

После этого нужно лишь заключить требуемый компонент(ы) внутри компонента границ ошибки.

<ErrorBoundary>
<MyComponent />
</ErrorBoundary>

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

Возникшая за границами ошибка отразится на всем дереве компонентов React и вызовет сбой приложения.

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

Например, метод жизненного цикла componentDidCatch(error, info) вызывается только в том случае, если ошибка возникает при рендеринге, в методах жизненного цикла или в конструкторах любой дочерней составляющей компонента.

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

2. Обработка ошибок с помощью Try-Catch: перехват за пределами границ

Блоки try-catch  —  не лучший способ обработки исключений на уровне компонентов, потому что они не перехватывают каскадно переданные из дочерних компонентов исключения.

Однако try-catch используются для обработки ошибок, которые не улавливаются границами.

Рассмотрим сценарий регистрации пользователя с асинхронным вызовом API. Для обработки возникающих ошибок здесь можно использовать try-catch и состояния React.

function SignUpButton(props) {
const [hasError, setError] = React.useState(false);
const handleClick = async () => {
try {
await api.signUp();
} catch(error) {
errorService.log(error)
setError(true);
}
}
if (hasError) {
return <p>Sorry, Sign up failed!</p>;
}
return <button onClick={handleClick}>Sign up</button>;
}

Аналогично try-catch можно использовать в ситуациях с неэффективными границами ошибок. Однако в некоторых случаях это может привести к дублированию кода.

3. Библиотека react-error-boundary 

Библиотека react-error-boundary значительно упрощает в React обработку ошибок. Она эффективно расширяет базовые возможности error boundaries.

Библиотека позволяет отображать резервный компонент, регистрировать ошибки так же, как в базовых error boundaries, и сбрасывает состояние приложения, чтобы ошибка снова не повторялась.

import {ErrorBoundary} from 'react-error-boundary'

function MyFallbackComponent({error, resetErrorBoundary}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}

export default function App() {
return (
<ErrorBoundary
FallbackComponent={MyFallbackComponent}
onError={(error, errorInfo) => errorService.log({ error, errorInfo })}
onReset={() => {
// Сброс состояния приложения
}}
>
<MyErrorProneComponent />
</ErrorBoundary>
);
}

Более того, теперь с помощью пользовательского хука useErrorHandler() можно обрабатывать ошибки в обработчиках событий и асинхронном коде.

Если хук use ErrorHandler (error) вызван со значением ошибки truthy, то react-error-boundary распространит это значение до ближайшей границы ошибки.

Поэтому react-error-boundary заменяет два разных методах обработки ошибок в приложении.


Лучшие методики регистрации ошибок

Многие разработчики знают о существовании журналов консоли, но далеко не все понимают, как нужно постоянно регистрировать ошибки в приложении React. Журналы консоли помогают в обнаружении корневых (root) ошибок при разработке, но они становятся неэффективными для приложения, выполняемого в браузере пользователя.

Поэтому необходимо создавать серверную службу регистрации ошибок или стороннюю службу их отслеживания.

Кроме того, анализ журнала возможно позволит разобраться во взаимодействии пользователей с приложением. Это полезно для распознавания их поведения и угроз безопасности.

Например, множественные неудачные попытки входа в систему могут свидетельствовать о желании получить несанкционированный доступ к приложению.

Инструменты

Для постоянной регистрации ошибок можно использовать сторонние сервисы регистрации на стороне клиента. Ниже приведены одни из лучших провайдеров.

Sentry

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

redux-logger

redux-logger также известен как Logger for Redux. Он позволяет пользователю создать свой собственный регистратор с настраиваемыми параметрами. Он прост в использовании и позволяет настраивать ведение журнала для консоли и уровня производительности.

Log level

Log level заменяет стандартный console.log(), имеет функции ведения журнала и фильтрации на основе иерархии уровней, которые предоставляют гораздо больше контроля над журналами. В сравнении с Sentry или redux-logger, у него меньше функций, но есть основные, чаще всего необходимые.

Можно использовать loglevel-plugin-remote для отправки журналов на удаленный сервер для хранения, анализа и предупреждений. Журналы следует отправлять в виде объектов JSON, а не обычным текстом, чтобы их можно было легко сортировать и упорядочить.


Заключение

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

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Manusha Chethiyawardhana: React Error Handling and Logging Best Practices

Предыдущая статьяPython PyQt5: современные графические интерфейсы для Windows, MacOS и Linux
Следующая статьяИнструменты DevOps: интерфейс Docketeer для Docker Desktop