跳到主要内容

第 25 章:错误处理——ErrorBoundary 与恢复

渲染期出错,React 不会让整个应用白屏,它会沿 fiber 树往上找"错误边界"(ErrorBoundary),让它兜住并渲染 fallback。本章看这个"兜底机制"在源码里长什么样。

先记住这一句throwException 先看抛出来的是什么:是 promise 就是挂起(走 Suspense,第 26 章),是真异常才是错误(沿 return 找最近的 ErrorBoundary)。边界用 getDerivedStateFromError 把错误变成 state,重渲染出 fallback。

一图流:抛异常后走哪条路

throwException:先分清"挂起"和"错误"

throwExceptionReactFiberThrow.js:362)是 render 阶段捕获异常的统一入口。它先看抛出的 value 是什么类型(379):

if (typeof value.then === 'function') {
// 这是 promise → 组件挂起了(Suspense 的机制)
const suspenseBoundary = getSuspenseHandler();
// 标记最近的 Suspense 边界切 fallback
} else {
// 这是真错误 → 标记 sourceFiber 为 Incomplete,走错误边界流程
}

同一套 throw 机制,两套处理:promise 是"我还没准备好,先展示 fallback",真异常是"这里崩了,找边界兜住"。这决定了 Suspense(第 26 章)和 ErrorBoundary 在源码里是兄弟。

找边界:沿 return 链往上

真错误时,sourceFiber 被打上 Incomplete370),然后沿着 return 链(第 3 章那个栈帧返回地址)往上找,看每个祖先是不是 Class 组件且实现了错误边界方法(getDerivedStateFromErrorcomponentDidCatch)。注意函数组件不能当边界,这正是 React 官方"ErrorBoundary 必须用 Class"限制的源码出处。

错误转 state:边界怎么重渲染

找到边界后,React 造一个错误更新(createClassErrorUpdate112),把错误塞进去,边界组件带着新 state 再渲染一次。这一轮里 getDerivedStateFromError 把错误转成 state.hasError = true,render 就返回 fallback UI。如果没有边界兜住,错误一路冒到根,走 root 的 onUncaughtError 回调(第 1 章 createRoot 的 options 里见过),由你决定怎么处理(记日志、上报)。

渲染外的错误:边界管不到

ErrorBoundary 只能兜 render 阶段的错误。事件回调、useEffectsetTimeout 里抛的错,不在渲染路径上,边界捕获不到,它们直接冒到全局(React 19 里通过 onCaughtError / onUncaughtError 配置)。这是新手最常见的误解:以为边界能兜住一切。它只兜"渲染一棵子树时"的错。

动手实验

  1. 看 throwException 分流:一个组件抛 new Error,一个 use 一个 pending promise,在 throwException(362)打断点,观察两次的 value 类型不同,走向不同分支。
  2. 验证边界只兜渲染getDerivedStateFromError 写日志,分别在 render 里和 onClick 里抛错,看前者命中边界、后者不命中。
  3. 看错误转 state:边界组件的 getDerivedStateFromError 打断点,观察它返回的 state 怎么被塞进下一次渲染。

小结

  • throwException(362)统一接住渲染异常,先分 promise(挂起)和真异常
  • 真错误沿 return 链找最近 Class 错误边界,函数组件当不了边界。
  • createClassErrorUpdate(112)把错误转成边界的新 state,重渲染出 fallback。
  • 没边界兜住就冒到根,走 onUncaughtError
  • 边界只兜 render 阶段,事件回调、effect 里的错管不到。
  • 第五部分到此收尾。下一步第六部分:Suspense 与并发特性(第 26 章)。