maesblog

Reactの「Error Boundary」について – 公式ドキュメント日本語訳

React v17 のリリースが差し迫ってきている今日この頃。私の場合、今年はほとんど React の開発案件に携わることがなかったので、React v16 の機能も完全に追いきれていません。React v17 がリリースされる前になんとか知識だけでも入れておこうと思って、React の公式ドキュメントを日本語に訳しています。前回まで、『Render Props』『Higher-Order Component』を訳しました(これらは v16 の機能ではないですが…)。今回は、React 16 で新たに導入されたコンポーネントのレンダリング中のエラーをハンドリングするための 『Error Boundaries』 を訳してみました。

In the past, JavaScript errors inside components used to corrupt React’s internal state and cause it to emit cryptic errors on next renders. These errors were always caused by an earlier error in the application code, but React did not provide a way to handle them gracefully in components, and could not recover from them.

これまで、コンポーネント内で発生した JavaScript のエラーは、React の内部の状態を壊し、次のレンダリング時に致命的な エラー発生させる原因となっていました。これらのエラーは、アプリケーションコードの初期の頃からのエラーによって常に引き起こされていましたが、React はコンポーネント内でそれらのエラーをうまくハンドリングする術を提供しておらず、それらのエラーから復旧することができませんでした。

Introducing Error Boundaries – Error Boundary とは

A JavaScript error in a part of the UI shouldn’t break the whole app. To solve this problem for React users, React 16 introduces a new concept of an “error boundary”.

UI の一部の JavaScript エラーにより、アプリ全体が壊れてはいけません。React ユーザーのためにこの問題を解決することを目的に、React 16 では、“error boundary” という新しいコンセプトを導入しました。

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

Error Boundary は、子コンポーネントのツリーの JavaScript エラーをキャッチし、それらのエラーのログを取り、クラッシュしたコンポーネントツリーの代わりに UI を フォールバックする React コンポーネントです。Error Boundary は、レンダリング中や、ライフサイクルメソッド内、それらの下に紐付くすべてのツリーのコンストラクタ内のエラーをキャッチします。

Note
Error boundaries do not catch errors for:
・Event handlers (learn more)
・Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
・Server side rendering
・Errors thrown in the error boundary itself (rather than its children)

注意
Error Boundary は、以下のエラーをキャッチしません

  • イベントハンドラー(詳細はこちら
  • 非同期のコード(たとえば、setTimeoutrequestAnimationFrame のコールバック)
  • サーバーサイドレンダリング
  • Error Boundary 自体にスローされるエラー(その子コンポーネントではなく)

A class component becomes an error boundary if it defines a new lifecycle method called componentDidCatch(error, info):

クラスコンポーネントに、componentDidCatch(error, info) と呼ばれる新しいライフサイクルメソッドを定義すると、Error Boundary になります。

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

  componentDidCatch(error, info) {
    // Display fallback UI
    // フォールバック 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
      // どんなカスタムフォールバック UI でもレンダリングできます
      return <h1>Something went wrong.</h1>;
    }
    return this.props.children;
  }
}

Then you can use it as a regular component:

以下のように通常のコンポーネントとして使うことができます。

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

The componentDidCatch() method works like a JavaScript catch {} block, but for components. Only class components can be error boundaries. In practice, most of the time you’ll want to declare an error boundary component once and use it throughout your application.

componentDidCatch() メソッドは JavaScript の catch {} ブロックのように機能しますが、コンポーネントに対して機能します。クラスコンポーネントのみ Error Boundary になり得ます。実際には、大抵の場合、Error Boundary コンポーネントを一度宣言して、アプリケーション全体で使用したくなるでしょう。

Note that error boundaries only catch errors in the components below them in the tree. An error boundary can’t catch an error within itself. If an error boundary fails trying to render the error message, the error will propagate to the closest error boundary above it. This, too, is similar to how catch {} block works in JavaScript.

Error Boundary は、ツリーにおけるその配下のコンポーネントのエラーのみをキャッチするということに注意してください。Error Boundary は、自分自身の中のエラーはキャッチできません。もし、Error Boundary がエラーメッセージをレンダリングすることに失敗した場合、エラーはその上にある Error Boundary に伝播されることになります。これは、JavaScript における catch {} ブロックがどのように機能するかに似ています。

componentDidCatch Parameters

error is an error that has been thrown.

error は、throw されたエラーです。

info is an object with componentStack key. The property has information about component stack during thrown error.

info は、componentStack key を持ったオブジェクトです。プロパティは、エラーが throw されている間のコンポーネントのスタックについての情報を持ちます。

//...
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);
}

//...

Live Demo – ライブデモ

Check out this example of declaring and using an error boundary with React 16.

React 16Error Boundary を宣言し、使用するこの事例をチェックしてください。

Where to Place Error Boundaries – Error Boundaryをどこに配置するか

The granularity of error boundaries is up to you. You may wrap top-level route components to display a “Something went wrong” message to the user, just like server-side frameworks often handle crashes. You may also wrap individual widgets in an error boundary to protect them from crashing the rest of the application.

Error Boundary の粒度はあなた次第です。ユーザーに対して「何かが間違っている」というメッセージを表示するためにトップレベルのルートコンポーネントをラップしてもよいでしょう。ちょうどサーバーサイドのフレームワークが、よくクラッシュをハンドリングするのと同じようにです。さらに、アプリケーションの残りの部分をクラッシュから守るために、Error Boundary 内で個別にウィジェットをラップすることもできます。

New Behavior for Uncaught Errors – Uncaught Errors のための新たな振る舞い

This change has an important implication. As of React 16, errors that were not caught by any error boundary will result in unmounting of the whole React component tree.

今回の変更は重要な意味が込められています。React 16 になって、Error Boundary によって Uncaught エラーは、結果として React のコンポーネントツリー全体のアンマウントを引き起こします。 

We debated this decision, but in our experience it is worse to leave corrupted UI in place than to completely remove it. For example, in a product like Messenger leaving the broken UI visible could lead to somebody sending a message to the wrong person. Similarly, it is worse for a payments app to display a wrong amount than to render nothing.

われわれはこの決定について熟慮しましたが、われわれの経験上、壊れた UI を適当な場所に残すよりも完全に取り除いた方がよいということがわかっています。たとえば、メッセンジャーのようなプロダクトで、壊れた UI を見える状態にしておくと、ユーザーによっては間違った人にメッセージを送ってしまうということを引き起こしかねません。同様に、支払いアプリにとっては、何もレンダリングしないことよりも、間違った合計を表示することの方がよくありません。

This change means that as you migrate to React 16, you will likely uncover existing crashes in your application that have been unnoticed before. Adding error boundaries lets you provide better user experience when something goes wrong.

今回の変更は、React 16 に移行する際に、これまで気付いていなかったあなたのアプリケーションに存在していたクラッシュが浮き彫りになるということを意味しています。Error Boundary を追加することによって、何か悪いことが起こった時でも、よりよいユーザー体験を提供できるようになります。

For example, Facebook Messenger wraps content of the sidebar, the info panel, the conversation log, and the message input into separate error boundaries. If some component in one of these UI areas crashes, the rest of them remain interactive.

たとえば、Facebookのメッセンジャーでは、サイドバーや情報パネル、会話履歴、メッセージ入力欄などのコンテンツを別々の Error Boundary の中にラップしています。もし、これらの UI 領域のひとつの中で コンポーネントのどれかがクラッシュしたとしても、他の部分はインタラクティブな状態をキープし続けます。

We also encourage you to use JS error reporting services (or build your own) so that you can learn about unhandled exceptions as they happen in production, and fix them.

われわれはまた、ハンドリングできていない例外を、プロダクションの中で発生した時に知り、対処できるために、JS のエラーレポーティングサービスを使用すること(または自分自身で構築すること)をお勧めします。

Component Stack Traces – コンポーネントのスタックトレース

React 16 prints all errors that occurred during rendering to the console in development, even if the application accidentally swallows them. In addition to the error message and the JavaScript stack, it also provides component stack traces. Now you can see where exactly in the component tree the failure has happened:

React 16 は、たとえアプリケーションがエラーを過って飲み込んだとしても、レンダリング中に発生したすべてのエラーを開発中のコンソールに表示します。エラーメッセージと JavaScript のスタックに加えて、コンポーネントのスタックトレースを表示します。これで、コンポーネントのどこでエラーが発生したか正確に確認することができます。

Error caught by Error Boundary component

You can also see the filenames and line numbers in the component stack trace. This works by default in Create React App projects:

さらに、コンポーネントのスタックトレースでは、ファイル名と行数も確認できます。これは Create React App プロジェクトでもデフォルトで機能します。

Error caught by Error Boundary component with line numbers

If you don’t use Create React App, you can add this plugin manually to your Babel configuration. Note that it’s intended only for development and must be disabled in production.

もし、Create React App を使っていなかったら、このプラグインを手動で Babel の設定ファイルに追加してください。なお、これは開発のためのものであり、プロダクションでは使用不可にしなければいけないことにご注意ください。

Note
Component names displayed in the stack traces depend on the Function.name property. If you support older browsers and devices which may not yet provide this natively (e.g. IE 11), consider including a Function.name polyfill in your bundled application, such as function.name-polyfill. Alternatively, you may explicitly set the displayName property on all your components.

注意
スタックトレース内に表示されるコンポーネント名は、Function.name プロパティに依存します。もし、まだこのプロパティをネイティブに提供していない(たとえば IE 11 などの)古いブラウザーやデバイスをサポートしているなら、function.name-polyfill のような Function.name のポリフィルをバンドルされたアプリケーションに含めることを考えてください。あるいは、すべてのコンポーネントに displayName プロパティを明示的にセットしてもよいでしょう。

How About try/catch? – try/catch はどうか?

try / catch is great but it only works for imperative code:

try / catch は優れていますが、命令型のコードでしか機能しません。

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

However, React components are declarative and specify what should be rendered:

しかし、React のコンポーネントは宣言的であり、何をレンダリングするべきか指定します。

<Button />

Error boundaries preserve the declarative nature of React, and behave as you would expect. For example, even if an error occurs in a componentDidUpdate method caused by a setState somewhere deep in the tree, it will still correctly propagate to the closest error boundary.

Error Boundary は、React の宣言的な性質を保持し、期待通りに振る舞います。たとえば、ツリーのどこか深いところの setState が原因となり、componentDidUpdate メソッド内でエラーが発生したとしても、最も近い Error Boundary に正しく伝播されます。

How About Event Handlers? – イベントハンドラーはどうか?

Error boundaries do not catch errors inside event handlers.

Error Boundary は、イベントハンドラーの中でエラーをキャッチしません

React doesn’t need error boundaries to recover from errors in event handlers. Unlike the render method and lifecycle methods, the event handlers don’t happen during rendering. So if they throw, React still knows what to display on the screen.

React は、イベントハンドラー内のエラーから復旧するために、Error Boundary を必要としません。render メソッドやライフサイクルメソッドとは異なり、イベントハンドラーはレンダリング中に発生しません。したがって、もしそれらが throw したとしても、React は、画面に何を表示するか今まで通り知っていることなります。

If you need to catch an error inside event handler, use the regular JavaScript try / catch statement:

もし、イベントハンドラー内のエラーをキャッチする必要があったとしたら、通常の JavaScript の try / catch 文をお使いください。

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  handleClick = () => {
    try {
      // Do something that could throw
      // 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>
  }
}

Note that the above example is demonstrating regular JavaScript behavior and doesn’t use error boundaries.

上記の例は、通常の JavaScript の振る舞いを示しており、Error Boundary を使用していないということに注意してください。

Naming Changes from React 15 – React 15 からの名前変更について

React 15 included a very limited support for error boundaries under a different method name: unstable_handleError. This method no longer works, and you will need to change it to componentDidCatch in your code starting from the first 16 beta release.

React 15 には、unstable_handleError という異なったメソッド名の元で Error Boundary の限定的なサポートが含まれていました。このメソッドはもう機能せず、React 16 β版の最初のリリースからコード内の unstable_handleErrorcomponentDidCatch に変更する必要が出てきます。

For this change, we’ve provided a codemod to automatically migrate your code.

今回の変更に際して、自動的にコードを移行できるように codemod を提供しています。

訳者まとめ

以上、『Error Boundary』の翻訳でした。記事自体はそれなりのボリュームでしたが、要するに、クラスコンポーネントに、ライフサイクルメソッドの componentDidCatch(error, info) メソッドを定義すると、Error Boundary として使用できるようになり、自分に紐付くすべてのコンポーネントのレンダリング中に起きたエラーをキャッチしてくれるようになる(自分自身のエラーはキャッチしません)ということです。

使用方法は、とてもシンプルです。以下は、実際に CodePen で試したサンプルです。

See the Pen React Error Boundary Sample by Takanori Maeda (@maechabin) on CodePen.

日本語の記事では、以下の記事がとてもわかりやすく説明されています。併せてチェックしてもらえればと思います。

関連記事

コメント

  • 必須

コメント