首页技术专题博客目录关于与联系

前端项目崩了3次,我终于学会了错误边界

线上崩了3次白屏,用户骂惨了。后来学会了 Error Boundary,配合错误上报,再也没崩过。

去年我们的前端项目线上崩了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 不是万能的,它捕获不了:

  1. 事件处理器里的错误
    // 捕获不到 <button onClick={() => { throw new Error() }}>点击</button>
  2. 异步代码里的错误
    // 捕获不到 setTimeout(() => { throw new Error() }, 1000);
  3. 服务端渲染的错误
  4. 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,强烈建议加上:

  1. 在路由层加 Error Boundary
  2. 在容易出错的组件外加 Error Boundary
  3. 配合错误上报系统使用
  4. 提供友好的降级UI

别等到线上白屏了才想起来。


推荐阅读:React 官方文档 - Error Boundary

评论区