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
本課重點回顧
- 錯誤邊界捕獲渲染階段的錯誤
- 目前只能用 Class Component 實作
- 推薦使用
react-error-boundary套件 - 事件處理中的錯誤用 try/catch 處理
- 細粒度包裹:每個獨立區塊用自己的錯誤邊界