React 错误边界是什么?能捕获哪些错误?
简化版
错误边界是 React 用来捕获子组件渲染过程错误的组件,避免整个应用白屏。它能捕获渲染、生命周期、构造函数中的错误,但不能捕获事件处理器、异步回调、服务端渲染错误。函数组件目前没有内置等价写法,通常用 class ErrorBoundary 或第三方库。
详细版
典型写法:
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
report(error, info);
}
render() {
if (this.state.hasError) return <Fallback />;
return this.props.children;
}
}
它适合包页面、路由、复杂区块和第三方组件,错误后显示降级 UI 并上报日志。
完整版教学
一、错误边界解决局部崩溃不拖垮整站
React 渲染过程中如果某个组件抛错,可能导致整棵组件树卸载。 错误边界提供一个兜底层,让错误限制在某个区域内。 这样用户至少能看到降级 UI,而不是整个页面白屏。
没有错误边界:
Chart 抛错 -> 页面整块崩溃
有错误边界:
Chart 抛错 -> Chart 区域显示 fallback
例如一个仪表盘有 6 个模块,图表模块异常时,不应该让导航、用户信息、其他 5 个模块全部不可用。 错误边界的价值就是缩小故障半径。
二、错误边界由两个 class 生命周期支撑
错误边界通常是 class 组件。
static getDerivedStateFromError 用来根据错误更新状态,显示 fallback。
componentDidCatch 用来记录错误日志。
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error: Error) {
return { hasError: true };
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
console.log(error, info.componentStack);
}
render() {
return this.state.hasError ? <div>出错了</div> : this.props.children;
}
}
一个负责“展示什么”,一个负责“记录什么”。
生产环境里通常会在 componentDidCatch 中上报 Sentry、日志平台或自研监控。
三、它能捕获渲染阶段错误,不能捕获所有错误
错误边界能捕获子组件渲染过程、生命周期、构造函数中的错误。
但它不能捕获事件处理器里的错误,也不能自动捕获 setTimeout、Promise 回调里的异步错误。
这是面试最常见陷阱。
function Button() {
return (
<button
onClick={() => {
throw new Error('click error');
}}
>
click
</button>
);
}
点击事件里的错误不会被错误边界按渲染错误那样捕获。 事件错误要在事件处理器中自己 try/catch,或交给全局错误监听和日志系统。 错误边界不是 JavaScript 通用异常捕获器。
四、边界放在哪里决定故障范围
错误边界可以包整个应用,也可以包页面、路由、组件区块。 包得太大,错误影响范围大;包得太小,边界太多,维护复杂。 工程里常见做法是路由级边界加关键区块边界。
<AppShell>
<ErrorBoundary fallback={<PageError />}>
<RoutePage />
</ErrorBoundary>
</AppShell>
| 放置位置 | 优点 | 风险 |
|---|---|---|
| 应用根部 | 最后兜底 | 一错整页 fallback |
| 路由级 | 页面隔离 | 页面内部仍可能整体降级 |
| 区块级 | 故障范围小 | 边界数量增加 |
如果有 10 个业务模块,至少路由级边界能保证一个页面错误不影响整个应用外壳。
五、错误边界要配合重试和重置
只显示“出错了”还不够。
用户可能需要重试,或者切换路由后重置错误状态。
否则边界一旦进入 hasError,可能一直显示 fallback。
function Fallback({ reset }: { reset: () => void }) {
return <button onClick={reset}>重试</button>;
}
常见策略:
错误发生
-> 展示 fallback
-> 上报日志
-> 用户点击重试或路由变化
-> 重置边界状态
很多团队会直接使用 react-error-boundary,因为它已经封装了 fallback、reset 等常用能力。
六、错误边界要和 Suspense 分清职责
Suspense 处理“还没准备好”,错误边界处理“准备失败或渲染失败”。 动态 import 加载中由 Suspense fallback 展示。 如果加载失败,错误边界负责展示错误 UI。
<ErrorBoundary fallback={<div>加载失败</div>}>
<Suspense fallback={<div>加载中</div>}>
<LazyPage />
</Suspense>
</ErrorBoundary>
记忆钩子:Suspense 接住“等一等”,Error Boundary 接住“坏掉了”。
这两个边界常一起出现,但职责不能混。 回答时能讲清这点,说明你理解现代 React 的边界设计。
七、常见误区与追问
- 误区:错误边界能捕获所有 JavaScript 错误。 它主要捕获子组件渲染阶段错误,事件处理器和异步回调不自动捕获。
- 误区:函数组件可以直接写 componentDidCatch。 错误边界生命周期目前属于 class 组件能力,函数组件通常用封装好的边界组件。
- 误区:根部包一个错误边界就够了。 根部兜底有必要,但关键页面和高风险区块也应有更细粒度边界。
- 追问:错误边界能捕获自己内部的错误吗? 它不能捕获自身抛出的错误,只能捕获子组件树中的错误。
- 追问:错误发生后如何恢复? 需要提供 reset 机制,例如点击重试、改变 key、路由变化时重置边界状态。
- 追问:错误边界和 try/catch 有什么区别? try/catch 捕获同步命令式代码,错误边界捕获 React 渲染树中的错误。
八、加强记忆
错误边界记成“React 渲染树的保险丝”。它用 getDerivedStateFromError 展示降级 UI,用 componentDidCatch 上报错误;能捕获子组件渲染错误,但不包事件和异步。最后补边界粒度、重试恢复、和 Suspense 的区别,答案就完整了。