第 25 章:错误处理——ErrorBoundary 与恢复
渲染期出错,React 不会让整个应用白屏,它会沿 fiber 树往上找"错误边界"(ErrorBoundary),让它兜住并渲染 fallback。本章看这个"兜底机制"在源码里长什么样。
先记住这一句:
throwException先看抛出来的是什么:是 promise 就是挂起(走 Suspense,第 26 章),是真异常才是错误(沿return找最近的 ErrorBoundary)。边界用getDerivedStateFromError把错误变成 state,重渲染出 fallback。
一图流:抛异常后走哪条路
throwException:先分清"挂起"和"错误"
throwException(ReactFiberThrow.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 被打上 Incomplete(370),然后沿着 return 链(第 3 章那个栈帧返回地址)往上找,看每个祖先是不是 Class 组件且实现了错误边界方法(getDerivedStateFromError 或 componentDidCatch)。注意函数组件不能当边界,这正是 React 官方"ErrorBoundary 必须用 Class"限制的源码出处。
错误转 state:边界怎么重渲染
找到边界后,React 造一个错误更新(createClassErrorUpdate,112),把错误塞进去,边界组件带着新 state 再渲染一次。这一轮里 getDerivedStateFromError 把错误转成 state.hasError = true,render 就返回 fallback UI。如果没有边界兜住,错误一路冒到根,走 root 的 onUncaughtError 回调(第 1 章 createRoot 的 options 里见过),由你决定怎么处理(记日志、上报)。
渲染外的错误:边界管不到
ErrorBoundary 只能兜 render 阶段的错误。事件回调、useEffect、setTimeout 里抛的错,不在渲染路径上,边界捕获不到,它们直接冒到全局(React 19 里通过 onCaughtError / onUncaughtError 配置)。这是新手最常见的误解:以为边界能兜住一切。它只兜"渲染一棵子树时"的错。
动手实验
- 看 throwException 分流:一个组件抛
new Error,一个use一个 pending promise,在throwException(362)打断点,观察两次的value类型不同,走向不同分支。 - 验证边界只兜渲染:
getDerivedStateFromError写日志,分别在 render 里和onClick里抛错,看前者命中边界、后者不命中。 - 看错误转 state:边界组件的
getDerivedStateFromError打断点,观察它返回的 state 怎么被塞进下一次渲染。
小结
throwException(362)统一接住渲染异常,先分 promise(挂起)和真异常。- 真错误沿
return链找最近 Class 错误边界,函数组件当不了边界。 createClassErrorUpdate(112)把错误转成边界的新 state,重渲染出 fallback。- 没边界兜住就冒到根,走
onUncaughtError。 - 边界只兜 render 阶段,事件回调、effect 里的错管不到。
- 第五部分到此收尾。下一步第六部分:Suspense 与并发特性(第 26 章)。