React Error Boundary 错误边界限制
1. 基本概念
1.1 什么是 Error Boundary
Error Boundary 是 React 16 引入的一个特性,它可以捕获子组件树中的 JavaScript 错误,记录错误并展示备用 UI,而不是让整个应用崩溃。大白话:嵌套组件某个组件出错时显示备用页面而非报错信息,影响整个页面显示
1.2 基本实现
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state,下次渲染时显示降级 UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 可以将错误日志上报给服务器
console.error('Error:', error);
console.error('Error Info:', errorInfo);
}
render() {
if (this.state.hasError) {
// 渲染降级 UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
2. 使用方式
2.1 基本用法
// 包裹可能出错的组件
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
// 嵌套使用
function ComplexApp() {
return (
<ErrorBoundary>
<div>
<ErrorBoundary>
<ComponentA />
</ErrorBoundary>
<ErrorBoundary>
<ComponentB />
</ErrorBoundary>
</div>
</ErrorBoundary>
);
}
2.2 自定义错误 UI
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>Oops! Something went wrong</h2>
<details>
<summary>Error Details</summary>
<pre>{this.state.error.toString()}</pre>
</details>
<button onClick={() => window.location.reload()}>
Refresh Page
</button>
</div>
);
}
return this.props.children;
}
}
3. 高级用法
3.1 错误恢复
class RecoverableErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
handleReset = () => {
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={this.handleReset}>
Try Again
</button>
</div>
);
}
return this.props.children;
}
}
3.2 错误日志上报
class LoggingErrorBoundary extends React.Component {
componentDidCatch(error, errorInfo) {
// 发送错误到日志服务
logErrorToService(error, errorInfo);
}
async logErrorToService(error, errorInfo) {
try {
await fetch('/api/error-logging', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
error: error.toString(),
componentStack: errorInfo.componentStack,
timestamp: new Date().toISOString()
})
});
} catch (loggingError) {
console.error('Failed to log error:', loggingError);
}
}
}
3.3 条件性错误边界
class ConditionalErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
// 只捕获特定类型的错误
if (error instanceof CustomError) {
return { hasError: true };
}
// 其他错误向上传播
throw error;
}
render() {
if (this.state.hasError) {
return this.props.fallback || <div>Error occurred</div>;
}
return this.props.children;
}
}
4. 实践模式
4.1 创建可重用的错误边界
// 创建通用错误边界组件
class ReusableErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div className="error-fallback">
{this.props.errorMessage || 'Something went wrong'}
</div>
);
}
return this.props.children;
}
}
// 使用示例
function App() {
return (
<ReusableErrorBoundary
fallback={<CustomErrorUI />}
errorMessage="Failed to load dashboard"
>
<Dashboard />
</ReusableErrorBoundary>
);
}
4.2 错误边界与 Suspense 结合
function App() {
return (
<ErrorBoundary>
<Suspense fallback={<Loading />}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
);
}
4.3 错误边界与 React Router 结合
function App() {
return (
<Router>
<ErrorBoundary>
<Switch>
<Route path="/dashboard">
<ErrorBoundary>
<Dashboard />
</ErrorBoundary>
</Route>
<Route path="/profile">
<ErrorBoundary>
<Profile />
</ErrorBoundary>
</Route>
</Switch>
</ErrorBoundary>
</Router>
);
}
5. 最佳实践
5.1 错误边界的粒度
function App() {
return (
<ErrorBoundary>
{/* 顶层错误边界,捕获通用错误 */}
<Layout>
<ErrorBoundary>
{/* 功能级错误边界 */}
<UserDashboard />
</ErrorBoundary>
<ErrorBoundary>
{/* 组件级错误边界 */}
<ComplexChart />
</ErrorBoundary>
</Layout>
</ErrorBoundary>
);
}
5.2 错误恢复策略
class SmartErrorBoundary extends React.Component {
state = { hasError: false, retryCount: 0 };
static getDerivedStateFromError(error) {
return { hasError: true };
}
handleRetry = () => {
this.setState(state => ({
hasError: false,
retryCount: state.retryCount + 1
}));
};
render() {
if (this.state.hasError) {
if (this.state.retryCount >= 3) {
return <div>Too many retries. Please refresh the page.</div>;
}
return (
<div>
<p>Something went wrong</p>
<button onClick={this.handleRetry}>
Retry (Attempt {this.state.retryCount + 1}/3)
</button>
</div>
);
}
return this.props.children;
}
}
6. 注意事项
6.1 错误边界的限制
- 不能捕获以下类型的错误:
- 事件处理器中的错误
- 异步代码中的错误
- 服务端渲染中的错误
- 错误边界自身抛出的错误
6.2 处理未捕获的错误
// 在应用入口处添加全局错误处理
window.addEventListener('error', (event) => {
console.error('Uncaught error:', event.error);
// 显示全局错误 UI 或重新加载页面
});
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
// 处理未捕获的 Promise 错误
});
7. 总结
7.1 使用场景
- 数据获取错误处理
- 组件渲染错误处理
- 复杂计算错误处理
- 第三方组件错误隔离
7.2 最佳实践建议
- 合理设置错误边界的粒度
- 实现适当的错误恢复机制
- 做好错误日志记录
- 提供友好的用户错误提示
- 考虑错误边界的性能影响