React Hooks: useTransition

说明

本文基于 v18.1.0 进行分析。

阅读本文需先阅读 React Hooks: hooks 链表、React Hooks: useState 分析。

调试基于 React Hooks: useState 内关于插队的代码。

TLNR

该 Hook 的返回值 startTransition 接收一个函数，函数内触发的更新都会被标记为低优先级，从而允许其它优先级更高的更新优先被处理。

mount 阶段的 useTranstion

对调试代码中的 useTranstion 打上断点，我们会在 mount 阶段时进入 mountTransition 这一函数。

Copy
function mountTransition(): [
boolean,
(callback: () => void, options?: StartTransitionOptions) => void,
] {
// useState 那篇文章内有介绍过，不再啰嗦
// 但这里可以看出 useTranstion 也会存在一个对应的 hook 实例（尽管是基于useState实现的）
const [isPending, setPending] = mountState(false);
// The `start` method never changes.
// 此处是关键实现，下面会分析
const start = startTransition.bind(null, setPending);
// hooks 链表里介绍过，不再啰嗦
const hook = mountWorkInProgressHook();
hook.memoizedState = start;
return [isPending, start];
}

从代码中不难发现，关键的分析点在于 startTransition，因此我们看看它的实现是什么。

Copy
// 删掉了一些不重要的代码
function startTransition(setPending, callback, options) {
// 创建变量缓存当前的 update 优先级
// 该优先级可被 getCurrentUpdatePriority 与 setCurrentUpdatePriority访问到
const previousPriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(
// 比较当前优先级与 ContinuousEventPriority 间谁更优先，并设置为当前的优先级
higherEventPriority(previousPriority, ContinuousEventPriority),
);
// 这里通过调用 useState 的 dispatch 函数，创建了较高优先级的 update 实例
setPending(true);

// 直接修改 ReactCurrentBatchConfig 的值，这将影响后续生成 update 的优先级
// 具体的代码可以看生成 update 实例时调用的 requestUpdateLane 函数
const prevTransition = ReactCurrentBatchConfig.transition;
ReactCurrentBatchConfig.transition = {};
const currentTransition = ReactCurrentBatchConfig.transition;

if (enableTransitionTracing) {
if (options !== undefined && options.name !== undefined) {
ReactCurrentBatchConfig.transition.name = options.name;
ReactCurrentBatchConfig.transition.startTime = now();
}
}
// 总的来说，上面对 ReactCurrentBatchConfig 的操作将会影响下面的 setPending 与 callback 内生成的 update 实例的优先级，而其优先级必然是低于第一次调用 setPending 所生成的 update 实例的优先级的
// 而低优先级的 update 又会在处理状态更新时被跳过，这里在 useState 的文章里也有提及
try {
setPending(false);
callback();
} finally {
// 这里是重置的操作，将优先级恢复到原先的状态
setCurrentUpdatePriority(previousPriority);

ReactCurrentBatchConfig.transition = prevTransition;

...
}
}

总结

很简单的一个钩子，总的原理便是将包裹在 callback 内所生成的更新打上低优先级的标记，从而让其他的更新优先处理。