跳到主要内容

第 26 章:Suspense 与 use——挂起与恢复

第 25 章说 throwException 会把 promise 和真异常分开。promise 那条路,就是 Suspense。本章把"挂起、fallback、恢复"三个动作在源码里对号入座。

<Suspense fallback={<Spinner />}>
<Profile /> {/* 内部 use(fetchProfile()) */}
</Suspense>

先记住这一句:Suspense 是三个动作的循环:挂起(渲染时抛 promise)→ fallback(边界捕获后渲染占位)→ 恢复(promise 完成,ping 触发 RetryLane 重试)use 让"在渲染里读异步"成为可能,是挂起的标准入口。

一图流:Suspense 的生命周期

挂起:渲染时抛出一个 promise

useReactFiberHooks.js:1151)拿到一个 promise 时,会把它抛出去。这个"抛"走进 throwExceptionReactFiberThrow.js:362),它一检查发现 value.then 是函数(384),就知道:这不是错误,是挂起。于是走"找最近的 Suspense 边界"分支(getSuspenseHandler399),当前这次渲染被标记为"挂起"。

注意抛 promise 的机制在第 25 章就出现了:Suspense 和 ErrorBoundary 共用同一个 throwException,只是抛的东西类型不同。这就是为什么 use 和普通错误能"天然区分"。

fallback:边界渲染占位

挂起标记打在哪,updateSuspenseComponentReactFiberBeginWork.js:2342)就看哪。它检查 DidCapture 标志(2357):子树挂起了,就把 showFallback 置真,这一次渲染走 fallback children(你的 <Spinner/>),primary 子树(<Profile/>)暂时不渲染。

恢复:promise 完成,ping 唤醒

promise 完成(数据到了)时,React 收到一个 ping:它找到当时挂起的边界,把一次重试更新排进 RetryLanesReactFiberLane.js:93,第 22 章那 4 条 Retry 车道)。重试更新以这个 lane 触发一次新渲染,updateSuspenseComponent 再次进来,这次 DidCapture 没了、promise 也有值了,showFallback 为 false,primary 子树重新渲染,fallback 退场。

这套"挂起 → 占位 → ping → 重试"就是 Suspense 的全部。并发渲染(第 23 章)下它更从容:挂起时不急着提交,可以等一等。

19 的变化:use 取代了大量手动边界

React 19 里,很多原本要手动写 Suspense 的场景被 use 简化了:use 能直接在渲染里读 promise,配合编译优化,许多边界被编译期优化掉了,开发者写 <Suspense> 的次数大幅减少。但边界背后的机制(挂起、fallback、ping、Retry)没变,只是更多被编译器代劳了。

动手实验

  1. 看挂起抛 promise:在 throwException(ReactFiberThrow.js:362)打断点,一个 use(fetch()) 的组件首次渲染,观察 value 是 thenable、走挂起分支。
  2. 看 showFallback 切换:在 updateSuspenseComponent(2342)打断点,首次渲染看 showFallback 变 true,数据到了的重试看它变回 false。
  3. 看 RetryLane:promise resolve 后,在调度处观察这次更新是不是走了 RetryLanes。

小结

  • Suspense = 挂起(抛 promise)→ fallback(showFallback)→ 恢复(ping → RetryLane)
  • use(1151)是挂起的标准入口,throwException(362)统一分流 promise 和真异常。
  • updateSuspenseComponent(2342)靠 DidCapture 决定渲染 fallback 还是 primary。
  • promise resolve 触发 ping,RetryLanes(93)驱动重试渲染。
  • 19 用 use 优化掉了大量手动边界,机制不变。
  • 下一步:useTransition 和 useDeferredValue 怎么配合并发,第 27 章。