React 18 并发渲染
如何让紧急交互优先于耗时的 UI 更新?
先记住一句话:并发渲染不是多线程,也不是让 React 在后台开一个线程;它允许 React 在 Render 阶段暂停、重做或放弃低优先级工作,把主线程优先留给更紧急的交互。
并发能力建立在 Fiber 架构之上,通常通过 createRoot 启用 React 18 的新根 API。它不是“所有更新自动变快”,而是让 React 获得更灵活的更新调度能力。
1. 为什么需要并发渲染?
搜索、筛选、图表和大列表等操作可能需要计算大量组件。如果输入更新和结果列表更新被当成同样紧急,列表渲染可能阻塞输入框,导致打字延迟。
并发渲染允许把一次交互拆成不同优先级:
React 可以先提交输入框的新值,暂缓结果列表的 Render;如果用户继续输入,旧的列表计算甚至可以直接放弃,避免把时间浪费在过时结果上。
2. 并发渲染如何工作?
React 仍运行在浏览器 JavaScript 主线程上,并发不等于并行。它主要依靠:
- Fiber 工作单元;
- 更新优先级;
- Scheduler 调度;
- 可中断、可重做的 Render;
- Commit 阶段的一致性提交。
可以简化为:
Render 过程中计算出的中间结果不会直接显示给用户。只有 Commit 后的结果才会成为页面状态,因此组件 Render 必须保持纯粹,不能在里面发送请求、修改 DOM 或执行不可撤销的副作用。
3. startTransition:标记非紧急更新
startTransition 用来告诉 React:回调中的 state 更新不是当前交互必须立刻完成的工作。
useTransition 获取 pending 状态
如果需要显示非紧急更新是否仍在进行,可以使用 useTransition:
isPending 适合展示“正在更新结果”的轻量提示,但不要用它替代接口请求本身的 loading 状态。
使用边界
- 控制输入框当前值的更新通常不能标记为 transition,否则输入响应可能延迟;
- transition 只改变 React 更新的优先级,不会让一个巨大的同步计算自动变成非阻塞计算;
- 如果计算本身很重,仍要优化算法、拆分组件、虚拟化列表或移到 Worker;
- transition 更新可能被打断,因此不要依赖它一定完成某个中间状态。
4. useDeferredValue:延后使用某个值
当你不能直接控制产生值的更新时,可以让某个值在渲染层面“稍后跟上”:
它不是传统防抖:
- 防抖通常使用固定时间延迟,并可能减少请求次数;
useDeferredValue根据 React 当前调度压力决定何时更新;- 系统空闲时它可能几乎立即更新,繁忙时则暂时使用旧值;
- 它不会取消网络请求,也不会自动缓存计算结果。
如果需要明确控制请求频率,仍应使用防抖、缓存或数据请求库;如果需要降低某段 UI 更新的优先级,可以考虑 transition 或 deferred value。
5. Suspense 与并发渲染
Suspense 为 React 能识别的未就绪内容提供 fallback 边界:
常见用途是 React.lazy 代码分割;部分框架和数据层还支持 Suspense 数据请求。并不是任意在 useEffect 中发起的 fetch 或 axios 请求都会自动触发 Suspense。
在支持 Suspense 的 SSR 环境中,Suspense 还可以配合流式渲染,让已准备好的内容先发送,未准备好的边界稍后补充。具体能力取决于所使用的框架和数据层。
6. 并发渲染与批量更新的区别
两者经常一起出现,但解决的问题不同:
React 18 的 createRoot 同时带来更广泛的自动批处理和并发相关能力,但批量更新本身不等于并发渲染。
7. 和 setTimeout 有什么区别?
setTimeout 只是把回调推迟到未来某个时间执行:
它适合做明确的时间延迟或防抖,但无法告诉 React 这次更新的优先级,也不能让 React 放弃已经过时的 Render 工作。
startTransition 则是在更新进入 React 后标记其优先级:
- 不创建后台线程;
- 不提供固定延迟;
- 不负责防抖或取消请求;
- 允许更紧急的更新优先处理;
- 允许 React 放弃尚未提交的过时结果。
8. React 17 与 React 18 的边界
React 17 没有公开的并发渲染 API,React 18 才通过新根 API 和相关 Hook 对外提供这些能力。但这不表示 React 17 完全不会调度任务,也不表示 React 18 中每次更新都会被中断。
是否能观察到并发效果,取决于:
- 是否使用
createRoot; - 更新是否被标记为 transition;
- Render 工作量是否足够大;
- 浏览器主线程是否繁忙;
- 是否使用支持 Suspense 的框架或数据层。
9. 常见误区
并发渲染等于多线程
不是。React 仍主要运行在主线程,通过调度和可中断 Render 让出执行机会。
startTransition 会让代码立即在后台执行
不会。它只是标记更新优先级,具体何时 Render 和 Commit 由 React 调度。
useDeferredValue 是防抖
不是。它没有固定等待时间,也不会自动减少接口请求次数。
transition 中的结果一定会显示一次
不一定。过时的 Render 可能被放弃,只有最终提交的结果才会显示。
并发渲染可以解决所有卡顿
不能。长时间同步计算、过大的 JSON 处理或复杂算法仍会阻塞主线程,需要从计算、数据量、组件拆分和 Worker 等方向解决。
总结
React 18 并发渲染可以概括为:
- 它不是多线程,而是可中断、可重做的 Render 调度能力;
startTransition用于标记非紧急 state 更新;useDeferredValue用于延后某个值在 UI 中的使用,不等于防抖;- Suspense 可以为支持的数据或代码加载提供 fallback;
- Render 可能被放弃,副作用只能放在事件或 effect 中;
- 复杂同步计算仍需要单独优化。
面试时可以这样回答:React 18 的并发渲染建立在 Fiber 之上,不是多线程,而是允许 React 按优先级调度可中断的 Render。用户输入等紧急更新可以优先提交,搜索列表等非紧急更新可以通过 startTransition 延后;useDeferredValue 则允许 UI 暂时使用旧值。只有最终完成的结果会进入 Commit,渲染阶段必须保持纯粹。

