1. 紧急更新 vs 过渡更新
React 18 引入了区分更新优先级的能力,详见 React v18.0 官方博文:
紧急更新(Urgent) 过渡更新(Transition)
────────────────────── ──────────────────────────
直接响应用户交互 UI 从一个视图过渡到另一个视图
例:输入框文字、按钮点击 例:搜索结果、标签页切换
优先级:SyncLane / 优先级:TransitionLane(14个并发)
InputContinuousLane
特点:必须立即响应, 特点:可以稍慢,用户不期望立即看到
否则用户感觉卡顿 可以被紧急更新中断2. useTransition
jsx
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
// 紧急:立刻更新输入框
setInputValue(e.target.value);
// 过渡:稍后更新搜索结果
startTransition(() => {
setSearchQuery(e.target.value);
});
};内部实现
javascript
// mountTransition 内部实现(ReactFiberHooks.js:3425)
function mountTransition() {
// 注意:用的是 mountStateImpl(不创建 dispatch),不是 mountState!
const stateHook = mountStateImpl(false);
// 内部 startTransition 的签名与公共 API 完全不同:
// 内部:startTransition(fiber, queue, pendingState, finishedState, callback, options)
// 公共:startTransition(scope)
const start = startTransition.bind(
null,
currentlyRenderingFiber,
stateHook.queue,
true, // pendingState: isPending 设为 true
false, // finishedState: transition 结束后 isPending 设为 false
);
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [false, start]; // mount 时 isPending 总是 false
}
// 内部 startTransition(ReactFiberHooks.js:3117)——不是 ReactStartTransition.js 中的公共 API!
function startTransition(fiber, queue, pendingState, finishedState, callback, options) {
// 1. 提升 update priority 至少到 ContinuousEventPriority
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(higherEventPriority(previousPriority, ContinuousEventPriority));
// 2. 设置 Transition 上下文(与公共 API 类似的逻辑)
const prevTransition = ReactSharedInternals.T;
ReactSharedInternals.T = { /* transition config */ };
try {
// 3. 乐观更新:先设置 pendingState(true)作为 TransitionLane 更新
dispatchOptimisticSetState(fiber, false, queue, pendingState);
// 4. 执行用户回调,里面的 setState 会被分配 TransitionLane
callback();
} finally {
ReactSharedInternals.T = prevTransition;
// 5. 恢复 update priority
setCurrentUpdatePriority(previousPriority);
}
// 6. 过渡结束后设置 finishedState(false)
// 通过 dispatchSetStateInternal 入队一个 thenable 或 finishedState
// 实现乐观更新的回滚
dispatchSetStateInternal(fiber, queue, finishedState, requestUpdateLane(fiber));
}startTransition 的本质是设置一个全局的 Transition 上下文标记。在这个上下文中调用的 setState,会被分配 TransitionLane 而不是 SyncLane。
handleChange 执行流程:
setInputValue(e.target.value)
→ dispatchSetState()
→ requestUpdateLane(fiber)
→ ReactSharedInternals.T === null(不在 transition 中)
→ 分配 InputContinuousLane(高优先级)
startTransition(() => {
setSearchQuery(e.target.value);
→ dispatchSetState()
→ requestUpdateLane(fiber)
→ ReactSharedInternals.T !== null(在 transition 中)
→ 分配 TransitionLane(低优先级)
});
结果:输入框立刻更新(高优先级),
搜索结果稍后更新(低优先级,可中断)3. useDeferredValue
jsx
const deferredQuery = useDeferredValue(searchQuery);
return <SearchResults query={deferredQuery} />;useDeferredValue 接收一个值,返回一个"延迟"版本。当值变化时:
- 立即渲染用旧值的结果(高优先级)
- 在后台用新值渲染(低优先级)
javascript
function updateDeferredValue<T>(value: T, initialValue?: T): T {
const hook = updateWorkInProgressHook();
// prevValue 来自上一轮渲染(alternate)的 memoizedState
// 不是当前 wip hook 的状态——因为 wip 状态即将被更新
const prevValue: T = currentHook.memoizedState;
return updateDeferredValueImpl(hook, prevValue, value, initialValue);
}
function updateDeferredValueImpl(hook, prevValue, value) {
// 如果值没变,直接返回(返回 value 不是 prevValue)
if (is(value, prevValue)) {
return value;
}
// 值变了
// 1. 先用旧值渲染(高优先级)
// 2. 安排用新值的低优先级更新
const lane = requestDeferredLane();
// ... 设置 hook.memoizedState 在低优先级渲染时才更新
}4. 中断和恢复
React 19 Concurrent Rendering Deep Dive 用生产场景演示了 Transition 被中断后恢复的完整流程,React 技术揭秘 则从源码层面解释了中断机制:
用户快速输入 "a", "ab", "abc" 的时间线:
T=0: 输入 "a"
→ setInputValue("a") [紧急]
→ startTransition(() => setSearchQuery("a")) [过渡]
→ 紧急渲染完成(输入框显示 "a")
→ 过渡渲染开始(用 "a" 搜索)
T=5ms: 输入 "ab"(过渡渲染还在进行中)
→ setInputValue("ab") [紧急]
→ startTransition(() => setSearchQuery("ab")) [过渡]
→ 紧急渲染完成(输入框显示 "ab")
→ 之前的 "a" 过渡渲染被中断!
→ 开始新的 "ab" 过渡渲染
T=10ms: 输入 "abc"("ab" 过渡还在进行中)
→ setInputValue("abc") [紧急]
→ startTransition(() => setSearchQuery("abc")) [过渡]
→ "ab" 过渡渲染被中断!
→ 开始 "abc" 过渡渲染
T=15ms: 过渡渲染完成
→ 显示 "abc" 的搜索结果
最终:输入框响应流畅,搜索结果只显示最终的 "abc"
中间的 "a" 和 "ab" 搜索结果被丢弃5. 14 个 TransitionLane 的意义
关于多个 Lane 的设计动机,What are Lanes in React? 做了深入分析:
javascript
// 为什么有 14 个 TransitionLane?
// 为了让多个并发的 transition 更新互不干扰
场景:同时有 2 个 startTransition
startTransition 1: setSearch("abc") → TransitionLane1
startTransition 2: setFilter("new") → TransitionLane2
如果只有 1 个 TransitionLane:
后来的更新会覆盖前一个的优先级信息
有多个 TransitionLane:
两个更新可以独立存在,各自被处理或中断下一步
- 并发渲染原理 — 并发特性的完整分析
- Scheduler 设计哲学 — Scheduler 如何调度不同优先级的任务