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