Error Boundary は React のv16から導入された機能で、これを使うとコンポーネント内で発生したエラーをキャッチすることが出来る。
主に、エラー用のUIを表示したり、エラーを記録したりすることに使われる。
前者にはstatic getDerivedStateFromErrorというメソッドを、後者にはcomponentDidCatchというメソッドを用いる。
Error Boundary のためのクラスコンポーネントを作り、そこにこれらのメソッドを定義して使う。
この記事では、バージョン16.8.6で動作確認している。
動作確認用のアプリの用意
以下の内容でアプリを作る。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Template htmlfile</title> </head> <body> <p>↓この下に React アプリが表示される↓</p> <div id="app"></div> <p>↑この上に React アプリが表示される↑</p> </body> </html>
import React from 'react'; import ReactDOM from 'react-dom'; const AquaChild = () => ( <div style={{backgroundColor: 'white'}}>aqua child</div> ); const Aqua = () => ( <div style={{backgroundColor: 'aqua', padding: '10px'}}> aqua <AquaChild /> </div> ); const Lime = () => <div style={{backgroundColor: 'lime'}}>lime</div>; const App = () => ( <> <Aqua /> <Lime /> </> ); ReactDOM.render(<App />, document.querySelector('#app'));
これをビルドすると以下のようなUIを持ったアプリが出来るので、これを対象に検証していく。

エラー用のUIを表示させる
React はv16から、発生したエラーがキャッチされなかった場合、コンポーネントツリー全体をアンマウントするようになった。
公式ドキュメントによればこれは、壊れたUIを表示することは何も表示しないことよりも悪いことである、という考えによるもの。
試しに、先程作ったサンプルでエラーを発生させてみる。
const Aqua = () => (
<div style={{backgroundColor: 'aqua', padding: '10px'}}>
- aqua
+ aqua{x}
<AquaChild />
</div>
);
Aquaのなかでxを参照しているが、xは存在しない変数なのでReferenceErrorが発生する。
この結果、アプリ全体が表示されなくなる。

エラーが発生したAquaだけでなく、Limeも含めたコンポーネントツリー全体がアンマウントされているのが分かる。
static getDerivedStateFromErrorを使ってエラーをキャッチすることで、ツリー全体がアンマウントされるのを防ぎ、適切なUIを表示させることが出来る。
Error Boundary を使うため、以下のコンポーネントを作成した。
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = {hasError: false}; } static getDerivedStateFromError(error) { console.log(error instanceof Error); return {hasError: true}; } render() { const {state, props} = this; if (state.hasError) { return ( <div style={{backgroundColor: 'orange'}}> 背景がオレンジのコンポーネントは ErrorBoundary によって表示されているエラー用のコンポーネントです。 </div> ); } return props.children; } }
static getDerivedStateFromErrorの引数で、キャッチしたエラーオブジェクトを取得できる。
そしてこのコンポーネントで、AquaとLimeをそれぞれラップする。
const App = () => ( <> - <Aqua /> - <Lime /> + <ErrorBoundary> + <Aqua /> + </ErrorBoundary> + <ErrorBoundary> + <Lime /> + </ErrorBoundary> </> );
こうすることで、AquaやLimeのなかでエラーが発生した際に、それをキャッチできる。
この状態でページを表示すると、今度は以下のようになっている。

Aquaで発生したエラーをキャッチしてエラー用のUIが表示されている。
その一方で、Limeではエラーが発生していないので、そのまま表示されている。
このように、Error Boundary を上手く使うことでアプリ全体がクラッシュするのを防ぎ、適切なUIをユーザーに提供できるようになる。
Error Boundary の直下ではなくもっと深い階層でエラーが発生しても、問題なくキャッチできる。
const AquaChild = () => ( - <div style={{backgroundColor: 'white'}}>aqua child</div> + <div style={{backgroundColor: 'white'}}>aqua child{x}</div> ); const Aqua = () => ( <div style={{backgroundColor: 'aqua', padding: '10px'}}> - aqua{x} + aqua <AquaChild /> </div> );
AquaではなくAquaChildでエラーを発生させても、先程と同じ表示になる。

コンポーネントのなかでエラーが発生すると、ツリーを上に辿っていき、一番最初に到達した ErrorBoundary がエラーをキャッチする仕組みになっている。つまり、JavaScript のcatch{}と同じような挙動である。
最後までキャッチされなかった場合は、既に述べたようにツリー全体がアンマウントされる。
Error Boundary の対象になるのは、配下のコンポーネントで発生したエラーのみ。
Error Boundary 自身のなかで発生したエラーは、キャッチすることが出来ない。
例えば以下のようにすると、キャッチできずにツリー全体がアンマウントされてしまう。
const AquaChild = () => ( - <div style={{backgroundColor: 'white'}}>aqua child{x}</div> + <div style={{backgroundColor: 'white'}}>aqua child</div> ); const App = () => ( <> <ErrorBoundary> <Aqua /> + <div>{x}</div> </ErrorBoundary> <ErrorBoundar
このエラーをキャッチしたければ、Appをラップする必要がある。
-ReactDOM.render(<App />, document.querySelector('#app')); +ReactDOM.render( + <ErrorBoundary> + <App /> + </ErrorBoundary>, + document.querySelector('#app') +);
そうするとAppの代わりにエラー用のUIが表示されるようになる。

エラーを記録する
エラーをログに残したり、エラー監視サービスなどに送信したりする場合は、Error Boundary にcomponentDidCatchメソッドを定義して、そのなかで行う。
return {hasError: true};
}
+ componentDidCatch(error, info) {
+ console.log(error);
+ console.log(info.componentStack);
+ }
+
render() {
const {state, props} =
第一引数で、キャッチしたエラーオブジェクトを取得できる。
第二引数のinfoはcomponentStackを持っており、ここには、エラーが発生したコンポーネントのスタックトレースが入っている。
以下は、AquaChildでエラーが発生した際のスタックトレース。
in AquaChild (at src/index.js:13)
in div (at src/index.js:11)
in Aqua (at src/index.js:21)
in ErrorBoundary (at src/index.js:20)
in App (at src/index.js:29)
static getDerivedStateFromErrorとの違いだが、公式のAPIリファレンスによれば、static getDerivedStateFromErrorはUIの描画のために使い、副作用を扱う場合にcomponentDidCatchを使うとよいらしい。
キャッチできないエラー
既に述べたように Error Boundary は自身のエラーをキャッチすることが出来ないが、配下のコンポーネントのエラーでもキャッチしないものが2つある。
1つ目は、イベントハンドラ内でのエラー。
以下のようにボタンを押した際にエラーが発生するようにした場合、レンダリング時にはエラーを出さないので、そのまま表示される。
<div style={{backgroundColor: 'aqua', padding: '10px'}}>
aqua
<AquaChild />
+ <button
+ type="button"
+ onClick={() => {
+ x;
+ }}
+ >
+ error button
+ </button>
</div>

そしてボタンを押すとエラーが発生するのだが、このエラーはキャッチされず、表示にも影響がない。
2つ目が、非同期処理のなかでのエラー。これも、エラーはどこにもキャッチされないまま終わり、アプリはそのまま表示され続ける。
const Aqua = () => { Promise.resolve().then(() => x); return ( <div style={{backgroundColor: 'aqua', padding: '10px'}}> aqua <AquaChild /> </div> ); };