在 React 18 之前,所有的状态更新都是“紧急的”——一旦触发更新,React 会立即开始渲染,中断当前页面操作,直接反映新状态到屏幕上。这在多数场景下没有问题,但碰到复杂计算或大数据渲染时,页面会出现明显的卡顿或延迟,影响交互体验。
React 18 引入了 Transitions 机制,允许开发者将某些更新标记为非紧急(低优先级),从而把宝贵的计算资源优先分配给用户的直接交互(如输入、点击),让应用保持流畅响应。
11.3.1 什么是“紧急更新”和“非紧急更新”
- 紧急更新(Urgent updates):直接响应用户交互,比如键盘输入、按钮点击、拖拽等。用户期望这些操作立刻反馈,任何延迟都会感觉“卡顿”。
- 非紧急更新(Transition updates):UI 从一个视图过渡到另一个视图,比如筛选后的列表结果、切换选项卡的内容、搜索结果展示。这类更新允许有一点延迟,用户愿意等待片刻以换取更流畅的整体体验。
React 通过 startTransition API 和 useTransition Hook 来区分这两类更新。
11.3.2 基本用法:startTransition
startTransition 是一个从 react 导入的顶层函数,用于包裹非紧急的状态更新:
import { useState, startTransition } from 'react';
function FilterableList() {
const [query, setQuery] = useState('');
const [filtered, setFiltered] = useState(allItems);
const handleChange = (e) => {
const value = e.target.value;
// 紧急更新:输入框的显示必须立刻响应
setQuery(value);
// 非紧急更新:筛选列表可以稍后处理
startTransition(() => {
const result = allItems.filter(item => item.includes(value));
setFiltered(result);
});
};
return (
<>
<input value={query} onChange={handleChange} />
<ExpensiveList items={filtered} />
</>
);
}
当用户在输入框中快速打字时,setQuery 是紧急更新,输入框中的文字会立即改变。而被 startTransition 包裹的 setFiltered 则被标记为“过渡任务”,React 会在空闲时处理,并且如果用户继续输入,之前的过渡任务可以被中断并丢弃,直接执行最新的过渡任务。这样界面上不会出现由于计算量大导致的输入卡顿。
11.3.3 useTransition Hook
useTransition 返回一个数组:[isPending, startTransition]。
isPending:布尔值,表示当前是否还有尚未完成的过渡更新,可用于在等待期间显示加载状态。startTransition:与顶层的startTransition相同,但关联了isPending状态。
import { useState, useTransition } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [result, setResult] = useState(initialData);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value); // 紧急更新
startTransition(() => {
setResult(fetchSearchResults(value)); // 模拟开销大的更新
});
};
return (
<div>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ResultsList data={result} />
</div>
);
}
当用户快速输入时,isPending 会变为 true,可以显示一个加载指示器。但这个指示器只会在过渡更新真正花费较长时间时才会被用户感知,在大多数性能好的设备上几乎瞬间完成。
11.3.4 Transitions 的底层原理
React 18 的并发渲染(Concurrent Rendering)是 Transitions 的基础。并发模式下,React 可以将渲染任务拆分为小的时间片,并赋予不同优先级。
- 紧急更新(如
setState直接调用)被放在同步通道或高优先级任务中,立即调度。 - Transition 更新被标记为低优先级任务。当高优先级任务(如用户输入)到达时,正在进行的过渡渲染可以被中断,React 会丢弃当前 workInProgress 树,从新的状态重新开始渲染。
- 这种“可中断”特性保证了用户交互的即时响应,而复杂的视图更新可以在浏览器空闲时段逐步完成。
11.3.5 适用场景与最佳实践
适用场景:
- 搜索输入框:输入文字紧急更新,搜索结果列表过渡更新。
- 选项卡切换:切换标签时,内容区的复杂组件可以不阻塞选项卡的点击反馈。
- 大列表筛选、排序:保持按钮或下拉框的即时响应,让重渲染在后台进行。
- 路由导航:React Router 的
useNavigate内部也可配合 Transitions 使用,使页面切换不卡顿。
注意事项:
startTransition内部必须是同步的状态更新调用,不能是异步操作。- 不要将所有更新都包裹在 Transitions 中,只适用于确实存在性能问题或需要区分优先级的场景。
- 过渡更新必须是纯状态更新,如果包含副作用(如请求数据),请将异步请求放在
useEffect或其他地方,Transition 只负责根据已有的数据更新状态。 - 与
Suspense搭配使用时,过渡更新中的 Suspense 边界不会显示之前的回退 UI,而是保持当前界面直到新内容就绪,避免了闪烁。
11.3.6 实际收益
使用 Transitions 后,应用在复杂交互下的主观流畅度明显提升。用户会感觉页面“跟手”,即便后台正在进行大量计算。这是 React 从“性能优化”向“体验优化”迈进的重要一步,也是 React 18 并发渲染最亮眼的功能之一。
在代码实战中,你可以先确认哪些更新确实引发了卡顿,然后引入 useTransition,通过 isPending 给用户一个轻量的 Loading 提示,让体验更完整。