人人都会AI编程

11.1 并发渲染(Concurrent Rendering)机制

更新时间:2026-07-10

React 18 引入的并发渲染(Concurrent Rendering)并不是一个单独的功能,而是底层架构的一次重大升级。它赋予 React 在渲染过程中“分身”的能力——可以同时准备多个 UI 版本,并根据优先级决定哪个版本先呈现给用户。理解并发渲染,是掌握 React 18 及未来版本的关键。

11.1.1 回顾同步渲染的痛点

在 React 18 之前,状态更新一旦触发,React 会同步地完成整棵组件树的渲染,然后一次性提交到 DOM。这个过程是不可中断的:如果你的应用中有一个复杂的列表或图表需要大量计算,浏览器就会被阻塞,导致页面无法响应点击、输入等交互,使用者会感觉到明显的卡顿或掉帧

例如,在一个搜索框中输入文字,每次按键都可能触发列表重新过滤和渲染。如果列表项很多,渲染耗时超过 16ms(60fps 的单帧时间),输入框就会出现肉眼可见的延迟。这就是同步渲染在面对复杂 UI 时最大的问题——无法区分轻急缓重

11.1.2 并发渲染的核心能力

并发渲染将渲染过程变成可中断的异步过程。React 可以在内存中开始准备一棵新的组件树,其间如果出现更高优先级的更新(比如用户继续打字),它可以暂停当前工作,切换到新的更新,然后丢弃或复用之前的部分结果。

这带来的关键能力是:

  • 可中断渲染:长时间的渲染可以被高优先级更新打断并丢弃,不会阻塞主线程。
  • 时间切片(Time Slicing):React 把大块渲染工作切割成小片,分批执行,确保浏览器有时间处理用户输入和动画。
  • 优先级调度:不同类型的更新可以标记不同的紧急程度,比如键盘输入属于高优先级,后台数据预取属于低优先级。

这一切都建立在 Fiber 架构调度器(Scheduler) 之上——Fiber 让组件的渲染工作可以被拆分成小任务,Scheduler 负责决定何时执行哪个任务。

11.1.3 如何启用并发特性

从 React 18 开始,所有使用 createRoot 渲染的应用都会自动获得并发渲染的能力基础,但具体的并发行为需要通过特定的 API 显式使用。

// React 17: ReactDOM.render
// React 18: createRoot + render
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

如果没有 createRoot,应用的更新行为仍接近同步渲染(即 React 17 的遗留模式)。只有接入了 createRoot,后续介绍的 useTransitionuseDeferredValueSuspense 等并发特性才能真正发挥作用。

11.1.4 核心并发 API 实战

useTransition:标记非紧急更新

useTransition 允许你将某些状态更新标记为“非紧急”,从而避免它们拖慢用户正在进行的交互。

import { useState, useTransition } from 'react';

function SearchPage() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    // 紧急更新:输入框必须立即响应
    setQuery(e.target.value);

    // 非紧急更新:列表渲染可以稍后处理,如果用户继续输入就中断
    startTransition(() => {
      // 模拟过滤大型数据
      setResults(filterData(e.target.value));
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <div>刷新结果中...</div>}
      <ul>
        {results.map(item => <li key={item.id}>{item.text}</li>)}
      </ul>
    </div>
  );
}

在这里,用户输入时,setQuery 总是立即生效,保证输入框跟手。而 setResults 包裹在 startTransition 中,如果下一次按键到来时上一次的过滤还没完成,React 会中断它并直接开始新的过滤。isPending 让你可以在过渡期间显示加载指示。

useDeferredValue:延迟获取某个值的快照

useDeferredValue 是另一种标记非紧急的方式。它接受一个原始值,并返回该值的“延迟”版本。当原始值快速变化时,延迟版本会保持在旧值,直到 React 有空闲时间才更新。

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  const results = useMemo(() => filterData(deferredQuery), [deferredQuery]);

  return <ul>{results.map(...)}</ul>;
}

useTransition 不同的是,useDeferredValue 不直接控制状态更新,适合当子组件的数据依赖发生变化,而你无法直接控制该状态更新(例如从上层传入的 prop)时使用。

Suspense 与并发渲染的结合

虽然 Suspense 早在 React 16 就引入了(用于代码分割),但在 React 18 中,配合并发特性它变得更强大。当组件内部抛出 Promise 时,React 可以等待而不需要设置 loading 状态,并且在数据到达前保持旧 UI,实现无缝的加载体验。

<Suspense fallback={<Loader />}>
  <Comments />
</Suspense>

如果 Comments 组件使用了并发兼容的数据源(如集成了 React Query 的 use Hook 或 Next.js 的数据加载),React 在从缓存中获取数据时不会触发 fallback,只有首次加载或超时后才会显示 loading,避免了“闪烁”问题。

11.1.5 并发渲染对开发者的实际意义

  • 提升交互体验:告别输入卡顿、列表拖慢表单。
  • 优雅处理复杂状态:无需手动防抖或节流,React 自动管理中断与优先级。
  • 更自然的加载状态:通过 Suspense 和过渡,UI 可以从旧状态平滑过渡到新状态,而不是瞬间白屏。
  • 为 Server Components 和流式渲染奠基:并发架构使得服务端渲染也能分段传输,实现选择性注水。

11.1.6 常见误解与注意事项

误解 1:并发渲染会让你的应用变快。
实际上并发渲染不会减少需要渲染的总工作量。它只是让紧急任务更快被响应,从而让用户感觉更流畅。在极端计算密集的场景下,仍然需要配合 useMemoReact.memo 进行优化。

误解 2:所有更新都应该包裹在 transition 中。
只有当你需要区分输入反馈和随之而来的重渲染时,才应该使用 transition。简单的页面跳转、数据提交通常不需要。

注意: 并发渲染依赖于浏览器的时间片接口(如 setTimeoutMessageChannel),在服务端渲染环境中不能直接使用。不要在服务端组件中使用 transition 或 deferred value。

11.1.7 总结

并发渲染是 React 18 架构层面的革新,它将渲染从“一锤子买卖”变成了“可中断、可协调的持续过程”。通过 useTransitionuseDeferredValue,我们第一次可以显式地告诉 React:“这个更新不急,优先保证用户当前的交互。”

掌握并发渲染,你就能写出在复杂交互下依然流畅如丝的 React 应用。这一步从“能用”到“好用”,是高级开发者与普通使用者的分水岭之一。