在复杂的 React 应用中,任何组件都可能因未预见的异常而崩溃。如果不加处理,一个组件内部的错误会像雪崩一样导致整个组件树卸载,给用户呈现一片空白的界面。错误边界(Error Boundary)正是 React 提供的一种专门机制,用于捕获子组件树中发生的异常,并展示降级 UI,避免整个应用白屏。
10.5.1 错误边界是什么
错误边界是一个 React 组件,它通过实现 componentDidCatch 和 getDerivedStateFromError 两个生命周期方法(或其中之一),来捕获其子组件在渲染阶段、生命周期方法和构造函数中抛出的错误。错误边界无法捕获以下类型的错误:
- 事件处理函数中的错误(这部分需要用
try/catch手动处理) - 异步代码中的错误(如
setTimeout或Promise回调中的异常) - 服务端渲染期间的错误
- 错误边界自身抛出的错误
也就是说,错误边界主要针对渲染路径上的同步异常,确保 UI 不会整体崩溃。
10.5.2 实现一个错误边界
传统类组件可以通过生命周期方法实现错误边界。下面是一个典型的示例:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
// 更新 state,触发下一次渲染展示备用 UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 可以在这里将错误上报给服务器
console.error('ErrorBoundary caught:', error, errorInfo);
// 例如:reportErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 降级 UI,允许自定义 fallback
return this.props.fallback || (
<div style={{ padding: 20, textAlign: 'center' }}>
<h2>出现了意外错误</h2>
<p>请刷新页面重试</p>
</div>
);
}
return this.props.children;
}
}
核心方法说明:
static getDerivedStateFromError(error):静态方法,在子组件抛出错误后被调用,返回的对象会合并到 state 中。它应当只用来设置错误状态,不包含副作用(如网络请求)。componentDidCatch(error, errorInfo):同样在错误发生后调用,但允许执行副作用,比如把错误日志发送到监控系统。errorInfo.componentStack可以告诉你错误发生在哪个组件栈中。
使用时,只需用 ErrorBoundary 包裹可能出错的组件树:
function App() {
return (
<ErrorBoundary fallback={<FallbackUI />}>
<Sidebar />
<MainContent />
</ErrorBoundary>
);
}
10.5.3 在函数组件中使用错误边界
React Hooks 目前没有提供与类组件的 componentDidCatch 完全等价的官方 Hook。但我们可以通过包装类组件,或使用第三方库(如 react-error-boundary)来获得函数式的使用体验。
推荐:react-error-boundary
这个库提供了 ErrorBoundary 组件和 useErrorBoundary Hook,完全兼容函数组件生态:
npm install react-error-boundary
使用示例:
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>加载失败:{error.message}</p>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}
function MyComponent() {
// 组件可能抛出错误
if (Math.random() > 0.7) {
throw new Error('随机错误');
}
return <div>正常内容</div>;
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// 重置逻辑,比如清空状态重新加载数据
}}
>
<MyComponent />
</ErrorBoundary>
);
}
react-error-boundary 还支持 onError 回调用于日志上报,以及 resetKeys 属性自动重置错误边界,非常实用。
10.5.4 错误边界的实际应用场景
1. 模块级隔离
在大型应用中,不应只用一个顶层的错误边界包裹整个应用。更好的实践是对关键模块分别包裹错误边界,这样右侧栏崩溃不影响左侧内容区,功能卡片报错也不会让整个页面不可用。
<Layout>
<ErrorBoundary>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary>
<Content />
</ErrorBoundary>
</Layout>
2. 路由级保护
对于由路由驱动的应用,可以在每个路由组件外部包裹错误边界,当某个页面加载失败时只影响当前页面,其他路由仍然可用。React Router 的数据路由(Loader/Action)抛出的错误也可以通过错误边界统一处理。
3. 第三方组件隔离
当使用不可控的第三方组件时,它们可能因为特定数据或版本问题抛错。用错误边界包裹这些组件,可以保证即使第三方库出问题,也不拖垮整个应用。
4. 优雅降级与重试
结合重置功能,错误边界可以给用户“重试”选项,重新挂载组件树,尝试恢复正常。这对于网络波动导致的瞬时错误尤为有效。
10.5.5 注意事项与常见坑点
- 不能捕获事件处理中的错误:如果你的按钮点击处理函数中写错了属性访问,错误边界不会捕获。你需要在事件处理内部使用
try/catch或者将错误通过状态提升为渲染异常(例如throw在 render 中)。
- 不能捕获异步错误:
useEffect或setTimeout中的错误不会触发错误边界。对于异步操作,一定要在 Promise 链中.catch(),或者使用try/catch包裹await代码,并将错误设置为状态,以便触发错误边界。
- 捕获后组件树会完全卸载:当错误边界捕获到错误后,其子组件树会被完全卸载,并在降级 UI 的位置重新渲染。如果错误的子组件持有重要的状态,状态将会丢失。因此,考虑通过
resetKeys或key来强制重新挂载。
- 错误边界自身不能有异常:错误边界必须是一个健壮的组件,其自身的
render、getDerivedStateFromError和componentDidCatch都不应抛出错误,否则错误会向上冒泡到更外层的错误边界,甚至直接导致整个应用崩溃。
- 生产与开发环境差异:在开发模式下,React 会在错误边界捕获后仍然显示红色错误遮罩和调用栈,方便调试;而生产环境才会真正显示降级 UI。这可能会让你误以为错误边界没有生效,实际只是调试特性。
错误边界是构建健壮 React 应用的必备防线。合理划分边界层级,结合日志上报和用户友好的降级提示,可以显著提升应用在异常情况下的用户体验和可维护性。