【テクニカル・上級編】 Error Boundariesによるエラーハンドリング – React実践ガイド

Reactアーキテクチャの急所:Error Boundariesで「死なないアプリケーション」を設計する

Reactアプリケーションを開発していると、いつかは直面する現実がある。「なぜ、画面全体が真っ白になるのか?」という絶望的な問いだ。

JavaScriptのランタイムエラーがコンポーネントツリーの深部で発生したとき、Reactはその枝葉を切り落とすかのように、親コンポーネントまでエラーを伝播させ、最悪の場合はアプリケーション全体をアンマウントしてしまう。これはブラウザの実行コンテキストを守るための仕様だが、エンドユーザーにとっては「壊れたWebアプリ」以外の何物でもない。

今回は、この「予期せぬ死」を防ぎ、堅牢なUIを構築するための最前線である Error Boundaries について、現場の血と汗が染み込んだ知見を共有しよう。

—

1. なぜ「try-catch」では不十分なのか

Reactのレンダリングプロセスにおけるエラーは、宣言的なJSXの記述とコンポーネントのライフサイクルが密接に絡み合っている。`try-catch` は同期的なコードの局所的な保護には有効だが、ReactのFiber reconcilerが管理するコンポーネントツリーの再帰的なレンダリングフローを制御することはできない。

Error Boundariesは、Reactの内部ライフサイクルメソッドである `static getDerivedStateFromError` と `componentDidCatch` を活用することで、このレンダリングフローに介入し、エラーを「捕らえてUIを退避させる」ための境界線を引く仕組みだ。

実践的なError Boundaryの実装

import React from ‘react’;

// エラー境界は現状「クラスコンポーネント」でしか定義できない
// この制約はReactの内部アーキテクチャに起因するものだ
class GlobalErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}

// レンダリングフェーズでエラーが発生した際に呼び出される
static getDerivedStateFromError(error) {
// 状態を更新して、次のレンダリングでフォールバックUIを表示する
return { hasError: true };
}

componentDidCatch(error, errorInfo) {
// ここでSentryやDatadogのような監視ツールへエラーログを送信する
// 重要なのは、コンポーネントのスタックトレースを記録することだ
console.error(“ErrorBoundaryがエラーを捕捉:”, error, errorInfo);
}

render() {
if (this.state.hasError) {
// ユーザーに最低限の体験を保証するフォールバックUI
return

致命的なエラーが発生しました。再読み込みを試みてください。

;
}

return this.props.children;
}
}

—

2. アーキテクトが意識すべき「境界の粒度」

Error Boundariesの設計で最も陥りやすい罠は、「とりあえずルート直下に一つ置けばいいだろう」という安直な思考だ。

境界を広く取りすぎると、アプリケーションの一部で発生した小さなエラーが、画面全体のフォールバックを招く。これではユーザーの操作体験を大きく損なうことになる。逆に、過剰に細分化しすぎると、メモリ消費量の増大や、親子間の状態同期の複雑化を招く。

賢い境界設計の指針

1. コンテキスト分離: データフェッチを行う複雑なウィジェットや、サードパーティ製のライブラリを使用するコンポーネントの直近には必ず境界を置く。
2. 回復の可否: エラー発生時に、「再試行ボタン」を押せば復帰できるのか、それともセッション自体が汚染されているのかを見極める。
3. 非同期エラーの罠: Error Boundaryは、イベントハンドラや非同期通信(Promise)内のエラーはキャッチできない。これらは別途 `try-catch` や、React Queryなどのライブラリ側でのエラーハンドリングと組み合わせる必要がある。

—

3. パフォーマンスとメモリの観点から

Error Boundary自体は非常に軽量だが、これらを乱用すればFiberツリーが肥大化する。また、エラーが発生した際の再レンダリングプロセスでは、Reactはバグを含んだコンポーネントの破棄と、フォールバックUIの生成を同時に行う。

高度な最適化のヒント

  • リセットの戦略: `key` 属性を活用しよう。Error Boundaryがエラーを検知した後、ユーザーが「再試行」ボタンを押した際に、Boundaryの `key` を変更することで、Reactに強制的な再マウントを促すことができる。これにより、状態の汚染を確実にリセットできる。
  • Suspenseとの併用: `Suspense` と `ErrorBoundary` は兄弟のような関係だ。ローディング状態とエラー状態を正しく分離し、ツリーの各階層で適切に宣言的に記述することで、レンダリング負荷を最小限に抑えつつ、堅牢なUXを実現できる。

—

最後に:完璧なシステムなど存在しない

エンジニアとして覚えておいてほしいのは、「エラーは発生するもの」という前提でコードを書くのが、最も優秀なアーキテクトの姿勢であるということだ。

エラーを隠蔽するのではなく、エラーが起きたことをシステムが正しく認識し、ユーザーに透明性を持って伝え、可能な限り安全な場所まで避難させる。その「境界」を設計することこそが、堅牢なReactアプリケーションへの唯一の道だ。

今日から、あなたのアプリケーションを見渡してみてほしい。もし画面のどこかでエラーが発生したとき、あなたのアプリは優雅にフォールバックUIへ遷移できるだろうか。それとも、ただ静かに死を迎えるだろうか? 答えは、君の書いた境界線の中にある。

コメント

タイトルとURLをコピーしました