Skip to content
目录

Suspense 和 Concurrent Rendering

1. 版本与定位

能力首次对用户可见说明
<Suspense> + React.lazyReact 16.6客户端代码分割、加载占位
Concurrent Rendering(可中断 render)React 18 + createRoot依赖 Fiber + Scheduler 时间切片
Suspense 数据流 / 流式 SSR 增强React 18服务端可先出 fallback,再流式替换
use 读 PromiseReact 19与 Suspense 配合,见 04-Hooks §10

并发渲染不是多线程,而是同一条主线程上可打断、可恢复、可优先的 Reconciler 工作方式;Suspense 是「子树还没准备好时,边界内先显示 fallback」的协调协议。二者常一起出现,但概念上可分开理解。


2. Concurrent Rendering 是什么

2.1 心智模型

  • 同步模式(Legacy ReactDOM.render:一次更新从 render 到 commit 尽量跑完,长任务会堵住点击、滚动。
  • 并发模式(createRoot + Concurrent Features):Render 阶段可拆成多段,由 Scheduler 在段与段之间 shouldYield,高优 lane(如点击)可插队;Commit 仍一次性、不可打断
mermaid
flowchart LR
  subgraph sync [同步模式 17-]
    A1[更新] --> B1[整棵 render]
    B1 --> C1[commit]
  end
  subgraph conc [并发模式 18+]
    A2[更新] --> B2[render 片段]
    B2 --> Y{yield?}
    Y -->|| I[处理输入 / 更高优更新]
    I --> B2
    Y -->|| C2[commit]
  end

2.2 用户能感知到的收益

机制体验
时间切片大列表重渲染时输入仍跟手
Lane 优先级点击比 startTransition 里的筛选更快上屏
可丢弃的 render过时的 Transition 结果可被更新的 state 作废,减少「闪一下旧 UI」
useDeferredValue输入框即时、重内容稍后跟上

2.3 实现上靠谁(不贴长源码)

  1. workLoopConcurrentworkInProgress 存在时循环 performUnitOfWork,直到 shouldYield()(内部即 Scheduler 的 shouldYieldToHost)。
  2. continuation:一轮没遍历完,Scheduler 下次接着跑(见 05-Scheduler §3.3)。
  3. getNextLanes:从 root.pendingLanes 选出本轮要处理的最高优车道(五档见 02-Fiber §4.1)。

flushSync 仍走同步路径,故意绕过切片,用于必须与 DOM 立即一致的少数场景。


3. Suspense 是什么

3.1 职责边界

<Suspense fallback={...}> 只负责一件事:后代在「尚未就绪」时,由这一层决定显示 fallback 还是正式子树。常见「未就绪」来源:

场景典型 API版本
懒加载组件React.lazy + import()16.6+
异步数据(客户端)use(promise)19+
服务端异步 RSCServer Component await18+ 流式 / 19 稳定
资源加载(样式等)框架集成18+

3.2 基本用法

jsx
<Suspense fallback={<Loading />}>
  <AsyncTree />
</Suspense>

可嵌套:内层边界只兜底内层子树,外层 fallback 不会替代已就绪的内层内容。

3.3 与 Error Boundary 的分工

SuspenseError Boundary
触发子树 suspend(抛出 thenable / 特殊异常)子树 throw 普通 Error
UIfallback 加载态fallback 错误态
恢复数据/模块就绪后自动重试 render需用户操作或 state 重置

4. Suspense 实现原理(总结)

4.1 「挂起」不是 return,而是 throw

子组件在 render 中若发现 Promise 仍为 pending(use / 部分数据层),Reconciler 会抛出带 thenableSuspenseException(概念名,见 ReactFiberThenable.js),并带上 retryLane(常落入 RetryLanes,见 Fiber 文档辅助车道)。

这不是业务错误,而是控制流:向上冒泡直到最近的 Suspense 边界捕获。

4.2 边界内发生什么

updateSuspenseComponentReactFiberBeginWork.js)根据是否应显示 fallback:

  1. 显示 fallback:挂载 fallback 子树,给 Fiber 打上 DidCapture 等标记,正式子树暂缓或隐藏。
  2. 显示 primary:子树已就绪,渲染 children,隐藏 fallback。

Fiber 的 memoizedState(SuspenseState) 会记录是否处于「已超时显示 fallback」、retryLane、注水相关状态等,供下次更新与 SSR 对齐。

4.3 就绪后如何恢复

  • Promise resolve 后,在对应 retryLane 上调度一次更新。
  • 再次 render 时走 primary 路径;若仍 suspend,继续 fallback。
  • React 18+:服务端可对未就绪片段先流式输出 fallback HTML,再在数据就绪后追加 chunk(与 07-ReactServerComponents 衔接)。
mermaid
sequenceDiagram
  participant C as 子组件 render
  participant R as Reconciler
  participant S as Suspense 边界
  participant Sch as Scheduler
  C->>R: Promise pending
  R->>R: throw SuspenseException
  R->>S: 捕获,渲染 fallback
  Note over C: Promise resolve
  Sch->>R: retryLane 调度更新
  R->>S: 渲染 primary children

4.4 多个边界与「作用范围」

  • 只影响子孙;兄弟组件不因旁支 suspend 而卸载。
  • 嵌套时最近的边界先捕获;外层可继续显示自己的 fallback。
  • 列表里每项包一层 Suspense,可实现逐条就绪,避免整页一个 loading。

5. use 与 Suspense(React 19)

use(promise) 在 pending 时走与上节相同的挂起路径;fulfilled 返回值,rejected 抛错(由 Error Boundary 处理)。可在条件分支中调用,但不替代「数据应在哪一层 fetch」的架构选择。

useEffect + setState 相比:use 把异步纳入 render 协调,与 Suspense fallback 一体,而不是先空渲染再 effect 补数据。

详见 04-Hooks §10.3


6. Transition 与延迟值(React 18)

6.1 useTransition / startTransition

作用:把回调里触发的更新标为 TransitionLanes(非紧急),避免挡住 Sync / 连续输入。

要点

  • 返回 [isPending, startTransition];pending 表示 Transition 尚未完成。
  • startTransition 可在组件外调用;内部会临时提升/设置更新优先级上下文,执行 callback() 后恢复。
  • Actions(19) 结合时,异步 Action 也可在 Transition 中执行(见 04-Hooks §10)。

6.2 useDeferredValue

作用:对外仍用「上一拍」的 deferredValue 渲染重子树,在后台用 Transition 追上最新 value

与防抖区别:无固定延迟;与 React 调度统一,且可被更高优更新打断。

React 19:可选 initialValue,首屏先用初始值再 deferred 更新。


7. 并发 + Suspense 协作一览

用户操作Lane / 机制UI 表现
输入框打字Sync / Default即时
startTransition 改筛选Transition可先保持旧列表,新结果就绪后切换
进入懒加载路由Suspense + lazy边界内 fallback
use(fetch) 未返回Suspense + use同左
flushSyncSync,不 yield强制同步 commit,慎用

8. 常见误区

  1. Suspense 不是「加载组件」本身:要有子树真的 suspend;仅 isLoading 状态不必包 Suspense(当然可以用,但不是机制要求)。
  2. fallback 会替换整段子树:布局应用外层边界兜住,避免整页闪烁。
  3. 并发不等于后台线程:CPU 仍在主线程,靠 yield 改善响应。
  4. Commit 不会一半提交:不会出现「DOM 改了一半」的并发 commit;未完成 render 不会 partial commit 到页面(未完成树会被丢弃或继续 WIP)。

9. 源码阅读路径

主题文件
Suspense 组件ReactFiberSuspenseComponent.js
Thenable / 挂起异常ReactFiberThenable.js
beginWork 捕获ReactFiberBeginWork.js
并发循环 / yieldReactFiberWorkLoop.js
Lane / retryReactFiberLane.js

10. 总结

  1. Concurrent Rendering(18+):可中断的 Render,依赖 Scheduler 与 Lanes;Commit 仍原子。
  2. Suspense:通过 throw + 边界捕获 切换 fallback / primary,用 RetryLanes 在就绪后重试。
  3. use(19) 把 Promise 纳入同一套挂起协议。
  4. Transition / deferred 把「可慢半拍」的更新与紧急更新分离,改善交互。

05-Scheduler调度器07-ReactServerComponents 连读,可覆盖「客户端切片 + 服务端流式 Suspense」全链路。

参考

MIT License.