React Concurrent Mode 看起来像一次“架构升级”,实则是对 Web 渲染模型的重构尝试。它不是语法糖,不是性能优化技巧,而是一场调度范式的变革。
01. 页面卡顿的问题到底出在哪?
我们先抛开 Concurrent Mode,看一个大家都见过的 React 性能问题:
<input value={text} onChange={e => setText(e.target.value)} />
{expensiveComputation(text)}
你输入一个字母,页面就卡顿 300ms,CPU 一直飙高。这并不是 React 的错,而是你写了一个同步的、昂贵的计算,它和用户交互耦合在一起,React 的更新流程是阻塞型的:
- 用户触发
onChange→ 触发setState - React 立刻开始从头开始执行组件树
- 所有组件重新执行 → 触发 render → 生成 virtual DOM → diff → commit
如果中间有复杂组件渲染、DOM diff 过程超出 16ms,主线程直接卡住,输入就会延迟、掉帧、跳动、发抖,React 完全无能为力。
02. React 的大脑:调度器的局限性
React v16 之前是同步更新模式:任何 setState 都是立即开始处理,不可打断。
这种模式的问题是:
- 用户输入和 UI 渲染同处一条线程,无法优先响应交互
- 所有任务一口气干到底,没有“喘息”的机会
- 没有优先级区分(更新按钮颜色和输入响应都一样)
这本质是调度模型的问题:同步、不可抢占、不可中断。
03. Concurrent Mode:不再“干到底”,而是“做一会,看一看”
Concurrent Mode 改变的是调度系统:
- 抢占式调度:任务可以中断,优先执行用户交互
- 时间切片:任务被拆成若干小块,分帧处理
- 优先级模型:更新被标记不同优先级,点击/输入优先于动画或低优更新
- 可中断渲染:中途可以暂停,React 不再“一口气跑完组件树”
- 延迟更新(defer) :可以挂起某些更新,让用户体验更顺滑
这类似操作系统中的调度器,从“批处理”进化为“抢占式多任务”。
04. 举个栗子:input 卡顿如何解决?
经典场景是 input + 慢计算:
<input value={text} onChange={e => setText(e.target.value)} />
<ExpensiveComponent text={text} />
传统模式下,setText 触发后整个页面同步重渲,主线程被锁。
在 Concurrent 模式中,可以这样写:
const [text, setText] = useState('');
const [deferredText, setDeferredText] = useDeferredValue(text); // 🔥
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<ExpensiveComponent text={deferredText} />
</>
);
作用:让 input 响应优先,ExpensiveComponent 稍后再更新。
React 背后使用调度器为两类更新分配了不同优先级:
- 用户输入(高优)
- 重渲大型组件(低优)
05. React 到底“并发”了什么?
别被名字误导:React Concurrent Mode 并不是多线程。
它不是把任务放到 Worker,而是通过 requestIdleCallback、MessageChannel 等手段,实现在主线程上的任务切片与调度控制。
所以更准确的说法是:
React 并发模式 = 构建在单线程之上的“可中断调度机制”。
它模拟了一种“看起来像并发”的行为:React 可以在执行 render 过程中暂停、中断、恢复,而不是像以前一样“一刀切”。
06. React 并发机制下的新能力
除了响应式输入优化,Concurrent Mode 还开启了很多新可能:
🔹 Suspense for Data Fetching
const data = use(fetchData()); // 会自动挂起,等待加载完成
return <div>{data.title}</div>;
挂起(suspend)意味着:这个组件暂时不渲染,React 会优先处理其他更新或 fallback。
这要求 render 函数本身能被挂起/恢复,而不是一旦开始就必须跑完——这就是并发模式的基石。
🔹 Transitions
用户点击按钮跳转页面,但你希望保留当前视图直到加载完成:
const [isPending, startTransition] = useTransition();
startTransition(() => {
setRoute('/profile'); // 低优更新,允许中断
});
这种模式下:
- 点击事件处理是高优
- 页面跳转是可延迟的
- 加载中可以显示旧页面 + loading indicator
07. Cold Knowledge:为什么 Concurrent 模式迟迟没有默认启用?
虽然 React 18 开始默认启用部分并发特性(如自动批处理),但并没有默认开启完整的 Concurrent Mode(需要 <Suspense>、useTransition 等) 。
这是因为:
- 兼容性要求高:你的组件必须可中断、无副作用(比如在 render 中调用
Date.now()会出问题) - 旧生态不支持:大量 UI 库还未适配 concurrent 特性,像 Portal、Context、动画库
- 调试难度陡增:中断和恢复让开发者很难追踪 render 的执行流程
- 心理模型转变难:开发者习惯同步渲染,对于挂起、恢复、优先级不够敏感
一句话:Concurrent 是未来,但今天的大多数项目还没准备好。
08. 总结:React Concurrent Mode 解决了什么?
✅ 解决了主线程卡顿问题
✅ 提供了更好的交互体验(响应快)
✅ 支持数据请求的渐进式加载
✅ 开启了“时间切片”与优先级调度的前端范式
但也带来了新的挑战:
- 新的心理模型
- 更复杂的调度机制
- 对组件可中断性的要求
09. 再想一步:如果没有 Concurrent Mode,我们能否自己实现?
是的,社区已有一些“并发渲染”尝试,例如:
- preact signals
- Solid.js 的 fine-grained update
- Vue 3 中的
Suspense
但 React 是第一个系统性提出“调度优先级+中断恢复”的大厂实现,它对前端应用结构本身提出了新解法。
未来的 React 应用不会是“响应式 UI”,而是“带调度能力的协作式 UI”。
结语
React Concurrent Mode 是一次对前端渲染模型的重写,它不是“提升性能”这么简单,而是给予开发者更强的控制权:你可以明确告诉 React 哪个更新重要,哪个可以延后。
UI 不再是被动地等待数据和计算完成,而是主动调度,优雅降级,渐进展示。
它的难,不是技术,而是“需要你重新理解组件的生命周期、状态流转、更新机制”。
我们习惯了“组件就是函数”,但 Concurrent Mode 说:组件其实是一段可能暂停、重试、恢复的过程。
这就是它要解决的问题:前端不再只是“渲染”,而是“调度”。