C
React/上級/Lesson 20

Error Boundary — エラーハンドリング

30分·theory
このチャプター
1/2
TypeScript

Error Boundary — エラーハンドリング

💡 なぜ学ぶべきなのか?

🎯 1つのコンポーネントのバグがアプリ全体をクラッシュさせるのを防ぎます。
💼 ユーザーはエラー画面の代わりに「問題が発生しました」というメッセージだけを見ます。
本番環境では必須です。安定したサービスは就職の競争力につながります。
🏢 실무에서는
銀行アプリで送金機能にバグが発生した場合、Error Boundaryがなければアプリ全体が白い画面になります。Error Boundaryがあれば、送金部分だけに「一時的なエラーです」というメッセージが表示され、他の機能(残高照会など)は引き続き使用できます。

概念

Error Boundaryは、Reactのコンポーネントツリーでエラーが発生したときにアプリ全体がクラッシュするのを防ぎ、代わりにフォールバックUIを表示するエラー分離メカニズムです。本番環境ではユーザー体験を守り、安定したサービスを維持するために不可欠です。

なぜ重要なのか?

実際の運用環境では、サードパーティライブラリのバグや予期しないデータによるランタイムエラーが発生すると、画面全体が真っ白になることがあります。Error Boundaryがあれば、エラーが発生したセクションだけがエラーUIに置き換わり、ユーザーはサービスを使い続けることができます。また、エラーレポーティングと連携して障害検知やデバッグにも活用できます。

コアコンセプト

Error Boundaryは建物の防火壁のようなものです。ある部屋で火事が起きても、防火壁があれば他の部屋に燃え広がりません。ReactのError Boundaryも同様に、コンポーネントツリーの特定部分でエラーが発生してもアプリ全体が止まらないようにエラーを「分離」し、ユーザーには適切なフォールバックUIを表示します。

キーポイント

  • クラスコンポーネントでのみ実装可能(React 19時点)
  • 子コンポーネントのレンダリングエラーとライフサイクルメソッドのエラーのみをキャッチ
  • イベントハンドラ、非同期コード、サーバーサイドレンダリングのエラーはキャッチ不可
  • エラーログと連携して本番モニタリングに活用
💻 悪い例 — Error Boundaryのないコンポーネント
// エラー隔離のない危険なコンポーネント
const UserProfile = () => {
  const [user, setUser] = useState<User | null>(null);
  
  return (
    <div>
      <h1>{user.name}</h1> {/* userがnullの場合、アプリ全体がクラッシュ */}
      <img src={user.avatar} alt="プロフィール" />
      <ThirdPartyWidget userId={user.id} /> {/* サードパーティのエラー時にクラッシュ */}
    </div>
  );
};

// App.tsx
const App = () => {
  return (
    <div>
      <Header />
      <UserProfile /> {/* ここでエラーが発生すると、画面全体が真っ白になる */}
      <Footer />
    </div>
  );
};
💻 良い例 — 2025年推奨のError Boundaryパターン
// 現代的な Error Boundary クラス (TypeScript)
interface ErrorBoundaryState {
  hasError: boolean;
  error?: Error;
}

class ErrorBoundary extends Component<
  PropsWithChildren<{ fallback?: ReactNode; onError?: (error: Error) => void }>,
  ErrorBoundaryState
> {
  constructor(props: any) {
    super(props);
    this.state = { hasError: false };
  }

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

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    // エラーロギング (Sentry, LogRocket など)
    console.error('Error Boundary caught an error:', error, errorInfo);
    this.props.onError?.(error);
    
    // 運用環境ではエラーレポートサービスに送信
    if (process.env.NODE_ENV === 'production') {
      // Sentry.captureException(error, { contexts: { errorInfo } });
    }
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div className="error-fallback">
          <h2>問題が発生しました</h2>
          <p>しばらくしてからもう一度お試しください。</p>
          <button onClick={() => this.setState({ hasError: false })}>再試行</button>
        </div>
      );
    }

    return this.props.children;
  }
}

// 使用法 - 戦略的配置
const App = () => {
  return (
    <div>
      <Header />
      <ErrorBoundary 
        fallback={<UserProfileError />}
        onError={(error) => analytics.track('user_profile_error', { error: error.message })}
      >
        <UserProfile />
      </ErrorBoundary>
      
      <ErrorBoundary fallback={<div>おすすめ商品を読み込めません</div>}>
        <RecommendationSection />
      </ErrorBoundary>
      
      <Footer /> {/* エラーが発生してもFooterは表示され続ける */}
    </div>
  );
};
💻 上級例 — react-error-boundaryライブラリの活用
// 現場でよく使われる react-error-boundary ライブラリ
import { ErrorBoundary, withErrorBoundary } from 'react-error-boundary';

// カスタム fallback コンポーネント
const ErrorFallback = ({ error, resetErrorBoundary }: any) => {
  return (
    <div className="error-boundary-fallback" role="alert">
      <h2>エラーが発生しました</h2>
      <pre style={{ whiteSpace: 'normal' }}>{error.message}</pre>
      <button onClick={resetErrorBoundary}>再試行</button>
    </div>
  );
};

// HOC パターンでコンポーネントを包む
const SafeUserDashboard = withErrorBoundary(UserDashboard, { 
  FallbackComponent: ErrorFallback,
  onError: (error, errorInfo) => {
    // エラーリポーティング
    logErrorToService(error, errorInfo);
  },
});

// React Query と一緒に使用する現代的なパターン
const App = () => {
  return (
    <QueryClientProvider client={queryClient}>
      <ErrorBoundary
        FallbackComponent={ErrorFallback}
        onReset={() => {
          // エラーリセット時にキャッシュを無効化
          queryClient.invalidateQueries();
        }}
        resetKeys={['user']} // 依存関係が変更されると自動リセット
      >
        <UserDashboard />
      </ErrorBoundary>
    </QueryClientProvider>
  );
};

💡 ⚠️ よくある間違い

  • Error Boundaryをツリーの最上位にのみ配置し、エラー発生時にページ全体がフォールバックUIになってしまうケース
  • イベントハンドラや非同期コードのエラーもError Boundaryがキャッチしてくれると誤解するケース(それらには別途try-catchが必要)
  • エラーロギングなしにフォールバックUIを表示するだけにとどまり、本番中の問題把握が困難になるケース

💡 🎯 面接対策

Q: Error Boundaryがキャッチできるエラーとできないエラーの違いを説明してください。
Q: Error Boundaryをアプリケーション内でどのように戦略的に配置しますか?
Q: Error Boundaryとtry-catchの違いは何ですか?

ヒント: Error Boundaryはレンダリング中のエラーのみをキャッチし、イベント・非同期コードにはtry-catchが必要。戦略的な配置でエラーを分離してUXを保護。getDerivedStateFromErrorとcomponentDidCatchの役割を区別。エラーレポーティングとモニタリング連携の重要性を強調。

⚛️ Reactパターン — Error Boundaryエラーハンドリング

Error Boundaryのエラーハンドリングを、コードとともにステップごとに学んでいきましょう。
1 🧩 1. Error Boundaryエラーハンドリングが必要なシナリオ
この機能が必要な状況。
2 💻 2. コードの記述
Error Boundaryエラーハンドリングの基本的な使い方。
3 🎨 3. レンダリング結果
ユーザーが見る画面。
4 💡 4. 実務のヒント
よくある落とし穴とベストプラクティス。

🎮 Error Boundaryエラーハンドリング — ステップごとに理解する

各ステップをクリックして内容を読み、✓ 理解できましたボタンで進捗を確認してください。
🖥️ 実行結果 — レンダリングされたReactコンポーネント
✏️ React 코드 수정하기 (클릭해서 열기)
⚛️ React 18 + Babel Standalone — まず結果を確認し、エディタで自由に編集できます。

確認クイズ

Error Boundaryがキャッチできないエラーはどれですか?
💡 Error Boundaryは、レンダリング中、ライフサイクルメソッド、コンストラクターで発生したエラーをキャッチします。イベントハンドラー、非同期コード(setTimeout、Promise)、SSRのエラーはError Boundaryではキャッチされません。
先に読むとよい概念: React.memo / useMemo / useCallback
Error Boundary + Suspense - React