17.1 什麼是錯誤邊界?

錯誤邊界是一種 React 元件,可以捕獲子元件樹中的 JavaScript 錯誤,顯示降級 UI,而不是讓整個應用崩潰。

錯誤邊界無法捕獲的錯誤

可以捕獲 無法捕獲
渲染過程中的錯誤 事件處理器中的錯誤
生命週期方法的錯誤 非同步程式碼(setTimeout、fetch)
子元件樹中的錯誤 伺服端渲染
錯誤邊界自己的錯誤

17.2 建立錯誤邊界(Class Component)

錯誤邊界必須使用 Class Component(目前 Hook 還不支援 getDerivedStateFromError)。

import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null, errorInfo: null };
  }

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

  componentDidCatch(error, errorInfo) {
    console.error('ErrorBoundary caught:', error, errorInfo);
    // 在這裡可以送到錯誤追蹤服務(如 Sentry)
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || (
        <div style={{
          padding: '40px',
          textAlign: 'center',
          backgroundColor: '#fff5f5',
          borderRadius: '8px',
          margin: '20px',
        }}>
          <h2 style={{ color: '#c53030' }}>出了點問題</h2>
          <p style={{ color: '#718096' }}>
            {this.state.error?.message || '發生未知錯誤'}
          </p>
          <button
            onClick={() => this.setState({ hasError: false, error: null })}
            style={{
              padding: '8px 16px',
              backgroundColor: '#4299e1',
              color: 'white',
              border: 'none',
              borderRadius: '6px',
              cursor: 'pointer',
            }}
          >
            重試
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

export default ErrorBoundary;

使用

import ErrorBoundary from './ErrorBoundary';

function BuggyComponent() {
  throw new Error('我故意壞掉了!');
  return <p>不會顯示</p>;
}

function App() {
  return (
    <div>
      <h1>正常內容</h1>

      <ErrorBoundary fallback={<p>此區塊載入失敗</p>}>
        <BuggyComponent />
      </ErrorBoundary>

      <p>這段文字仍然會正常顯示!</p>
    </div>
  );
}

17.3 用 react-error-boundary 套件(推薦)

npm install react-error-boundary
import { ErrorBoundary } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div style={{
      padding: '20px',
      backgroundColor: '#fff5f5',
      border: '1px solid #fed7d7',
      borderRadius: '8px',
      margin: '16px',
    }}>
      <h3 style={{ color: '#c53030' }}>發生錯誤</h3>
      <p style={{ color: '#718096' }}>{error.message}</p>
      <button onClick={resetErrorBoundary}>重試</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onError={(error, info) => {
        console.error('Error logged:', error, info);
      }}
      onReset={() => {
        console.log('Error boundary reset');
      }}
    >
      <MyApp />
    </ErrorBoundary>
  );
}

17.4 事件處理器中的錯誤處理

錯誤邊界無法捕獲事件處理器中的錯誤,需要自行用 try/catch:

import { useState } from 'react';

function SafeButton() {
  const [error, setError] = useState(null);

  const handleClick = () => {
    try {
      throw new Error('按鈕點擊錯誤');
    } catch (err) {
      setError(err.message);
    }
  };

  if (error) {
    return <p style={{ color: 'red' }}>錯誤:{error}</p>;
  }

  return <button onClick={handleClick}>可能會出錯的按鈕</button>;
}

17.5 練習題

練習:建立一個帶有錯誤邊界的 Dashboard

  • Dashboard 由多個獨立的 Widget 組成
  • 每個 Widget 有自己的錯誤邊界
  • 一個 Widget 壞掉不影響其他 Widget
  • 可以個別重試載入失敗的 Widget

本課重點回顧

  1. 錯誤邊界捕獲渲染階段的錯誤
  2. 目前只能用 Class Component 實作
  3. 推薦使用 react-error-boundary 套件
  4. 事件處理中的錯誤用 try/catch 處理
  5. 細粒度包裹:每個獨立區塊用自己的錯誤邊界