去年我们的前端项目线上崩了3次。每次都是整个页面白屏,什么都看不到。
用户骂惨了,老板也急了:"能不能别让用户看到白屏?"
后来我才知道,这个问题叫"错误边界",React 早就提供了解决方案。
第一次崩溃:一个undefined毁了全站
第一次崩是因为后端接口返回的数据结构变了。
// 之前 { user: { name: 'Alice' } } // 现在 { user: null }前端代码里有这么一行:
const userName = data.user.name;一个 Cannot read property 'name' of null,整个页面白屏。
不只是用户资料页白屏,连导航栏都没了。用户啥都做不了,只能刷新。
第二次崩溃:第三方组件的锅
我们用了个开源的图表库。某天更新版本后,线上开始报错:
Uncaught TypeError: Cannot read property 'length' of undefined at ChartComponent.render图表组件崩了,又是白屏。
问题是,图表只是页面的一小部分,为啥会导致整个页面崩?
第三次崩溃:我开始重视这个问题
第三次崩是我自己的锅。
我在一个组件里用了 localStorage.getItem,但没处理 Safari 隐私模式下 localStorage 不可用的情况。
又是白屏。
这次老板直接找我谈话:"能不能保证一个小错误不会导致整个页面挂掉?"
我说:"能,用 Error Boundary。"
什么是 Error Boundary
Error Boundary 是 React 16 引入的特性,用来捕获组件渲染时的错误。
它就像 JavaScript 的 try-catch,但是用在 React 组件树上。
class ErrorBoundary extends React.Component { state = { hasError: false }; static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('捕获到错误:', error, errorInfo); } render() { if (this.state.hasError) { return <div>出错了,请刷新页面</div>; } return this.props.children; } }用法:
<ErrorBoundary> <MyComponent /> </ErrorBoundary>如果 MyComponent 抛错了,Error Boundary 会捕获错误,显示降级UI,而不是让整个页面白屏。
我是怎么用的
1. 在路由层包裹
我在每个路由外面加了 Error Boundary:
<Router> <Route path="/home" element={ <ErrorBoundary fallback={<ErrorPage />}> <HomePage /> </ErrorBoundary> } /> </Router>这样,某个页面崩了,只会显示错误页面,不会影响其他页面。
2. 在关键组件外包裹
有些组件特别容易出错,比如:
- 第三方图表组件
- 富文本编辑器
- 复杂的表单
我会单独给它们包一层:
<ErrorBoundary fallback={<div>图表加载失败</div>}> <Chart data={data} /> </ErrorBoundary>这样,图表崩了也不会影响页面其他部分。
3. 上报错误
光捕获错误还不够,还要知道线上到底出了什么错。
componentDidCatch(error, errorInfo) { // 上报到 Sentry Sentry.captureException(error, { contexts: { react: errorInfo } }); }现在每次线上报错,我都能在 Sentry 后台看到:
- 错误信息
- 组件堆栈
- 用户浏览器信息
- 复现步骤
Error Boundary 的局限性
Error Boundary 不是万能的,它捕获不了:
- 事件处理器里的错误
// 捕获不到 <button onClick={() => { throw new Error() }}>点击</button> - 异步代码里的错误
// 捕获不到 setTimeout(() => { throw new Error() }, 1000); - 服务端渲染的错误
- Error Boundary 自己的错误
这些错误需要用其他方法处理:
- 事件处理器:手动
try-catch - 异步代码:
window.onerror或 Promise rejection handler
更好的降级体验
一开始,我的 Error Boundary 只显示"出错了"。后来我优化了降级UI:
function ErrorFallback({ error, resetError }) { return ( <div className="error-fallback"> <h2>😢 页面出现了问题</h2> <p>我们已经记录了这个错误,会尽快修复</p> <button onClick={resetError}>重试</button> <a href="/">返回首页</a> </div> ); }给用户提供了"重试"和"返回首页"的选项,体验好多了。
函数组件怎么用?
Error Boundary 只能用 class 组件写,但可以用 react-error-boundary 这个库:
import { ErrorBoundary } from 'react-error-boundary'; function ErrorFallback({ error, resetErrorBoundary }) { return ( <div> <p>出错了: {error.message}</p> <button onClick={resetErrorBoundary}>重试</button> </div> ); } <ErrorBoundary FallbackComponent={ErrorFallback}> <MyComponent /> </ErrorBoundary>功能更强大,支持重试、onError 回调等。
现在的效果
用了 Error Boundary 之后:
- 白屏问题彻底解决了
- 用户投诉少了80%
- 能第一时间发现线上问题
- 降级体验也做得更好了
虽然还是会有错误,但至少不会让整个页面挂掉。
总结
如果你的 React 项目还没用 Error Boundary,强烈建议加上:
- 在路由层加 Error Boundary
- 在容易出错的组件外加 Error Boundary
- 配合错误上报系统使用
- 提供友好的降级UI
别等到线上白屏了才想起来。
评论区