React useTransition 源码

这篇文章发表于 阅读 3

useTransition 是React18提供的新Hook,可让您在不阻塞 UI 的情况下更新状态。

const [isPending, startTransition] = useTransition();
  • isPending: 告诉组件更新是否在pending中; 调用startTransition就设置为true,直到回调函数执行前才设置为false。
  • startTransition: 该函数用来将状态更新标记为过渡,同时通过回调来延迟更新状态。

源码

useTransition

在mount阶段调用mountTransition,返回[pending, startTransition],调用startTransition来实现功能。

function mountTransition(): [ boolean, (callback: () => void, options?: StartTransitionOptions) => void, ] { const stateHook = mountStateImpl((false: Thenable<boolean> | boolean)); const start = startTransition.bind( null, currentlyRenderingFiber, stateHook.queue, true, // pending状态,调用 startTransition 时,pending就为true false, // 当startTransition中callback要执行时pending的状态就设置为false,表示不是pending状态了。 ); const hook = mountWorkInProgressHook(); // 将 start 放在hook的memoizedState上,在更新阶段就从memoizedState上取,不会重新生成 hook.memoizedState = start; return [false, start]; // isPending的默认值为false }

startTransition分为三次更新,下面标记为update1update2update3

  1. update1: 设置pending=true,表示处于pendin状态(以React事件优先级更新,优先级不低于ContinuousEventPriority
  2. update2: 设置pending=false,表示处于pendin状态结束,开始处理业务(以TransitionLanes优先级)
  3. udpate3: 调用回调函数,真正处理业务的地方(以TransitionLanes优先级)

startTransition的流程如下:

  1. 保存以前的更新优先级previousPriorityprevTransition;设置本次更新的优先级(最低为持续更新ContinuousEventPriority优先级,保证update1能尽快更新);
  2. 设置 ReactCurrentBatchConfig.transition = null,保证第一次更新是以高优先级更新(标记为update1的地方),pending状态就会设置为true;同时设置ReactCurrentBatchConfig.transition = currentTransition,后续的update2udpate3就是通过该值确定为TransitionLanes优先级。
  3. 接着执行dispatchSetState(fiber, queue, finishedState)(标记为update2),设置pendingfalse;优先级为TransitionLanes
  4. 执行callback(标记为update3),优先级和udpate2一样为TransitionLanes优先级(TransitionLanes优先级有15种,所以update2和update3优先级是不同的),所以状态不会立即更新,要等到其他高优先级的任务先执行,
  5. 还原优先级。

可以看出update1分配了不低于ContinuousEventPriority的优先级(比如:如果实在click事件中调用就是DiscreteEventPriority优先级),update2update3分配了TransitionLanes优先级,

PS: 如果用户一直调用startTransition,新的udpate1就会打断上一次的update2udpate3pending状态就一直为true,直到知道上一次的udpate2update3过期。

function startTransition<S>( fiber: Fiber, queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>, pendingState: S, finishedState: S, callback: () => mixed, // 业务设置的回调函数 options?: StartTransitionOptions, ): void { // 保存以前的优先级,设置持续事件优先级,最后恢复成previousPriority优先级 const previousPriority = getCurrentUpdatePriority(); setCurrentUpdatePriority( higherEventPriority(previousPriority, ContinuousEventPriority), ); // 保存以前的 transition, 设置新的transition;最后恢复prevTransition const prevTransition = ReactCurrentBatchConfig.transition; const currentTransition: BatchConfigTransition = { _callbacks: new Set<(BatchConfigTransition, mixed) => mixed>(), }; if (enableAsyncActions) { ReactCurrentBatchConfig.transition = currentTransition; dispatchOptimisticSetState(fiber, false, queue, pendingState); // pendingState } else { ReactCurrentBatchConfig.transition = null; dispatchSetState(fiber, queue, pendingState); // update1: pendingState: 在mountTransition中设置为true,表示处理中pending ReactCurrentBatchConfig.transition = currentTransition; // 后面的upadte2、udpate3 的优先级就是transition的优先级,比较低,可以被打断,就不会阻塞高优先级(比较用户输入,点击事件)。 } // .... try { if (enableAsyncActions) { // .... } else { // // 关闭pending,表示开始处理 // 会根据ReactCurrentBatchConfig.transition来判断是否存在transition,同时给本次更新分配transition的优先级 dispatchSetState(fiber, queue, finishedState); // update2: // 真正执行业务回调的地方 callback(); // udpate3: } } catch (error) { // .... } finally { // 还原优先级,因为前端将优先级设置为transition的优先级了 setCurrentUpdatePriority(previousPriority); // 还原transition ReactCurrentBatchConfig.transition = prevTransition; // .... } }

transition优先级分配机制

dispatchSetState 中通过requestUpdateLane()获取优先级。

上面udpate2update3的优先级与udpate1不同的的原因在于requestUpdateLane中通过ReactCurrentBatchConfig.transition来判断是否分配TransitionLanes优先级;

export function requestUpdateLane(fiber: Fiber): Lane { // .... const transition = requestCurrentTransition(); // 就是获取 ReactCurrentBatchConfig.transition,在startTransition里面添加的 if (transition !== null) {// 当前存在的transition, 就获取对应的lane const actionScopeLane = peekEntangledActionLane(); return actionScopeLane !== NoLane ? actionScopeLane : requestTransitionLane(transition); // requestTransitionLane 中为transition分配优先级 } // ... }
  1. requestTransitionLane中通过claimNextTransitionLane获取优先级。

transition的优先级一共有15种,初始值为TransitionLane1,每次分配一个比上次低一级的优先级直到TransitionLane15,如果分配完了就重新从TransitionLane1开始分配,一直循环。

export function claimNextTransitionLane(): Lane { // nextTransitionLane的初始值为 TransitionLane1 const lane = nextTransitionLane; // 每次向左移一位 nextTransitionLane <<= 1; if ((nextTransitionLane & TransitionLanes) === NoLanes) { // 如果分配完了就从 TransitionLane1 开始重头开始分配 nextTransitionLane = TransitionLane1; } return lane; }

React优先级

  1. lane优先级(共31种)
export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000; export const NoLane: Lane = /* */ 0b0000000000000000000000000000000; export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001; export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010; export const SyncLaneIndex: number = 1; export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100; export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000; export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000; export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000; const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000; const TransitionLanes: Lanes = /* */ 0b0000000001111111111111110000000; const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000; const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000; const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000; const TransitionLane4: Lane = /* */ 0b0000000000000000000010000000000; const TransitionLane5: Lane = /* */ 0b0000000000000000000100000000000; const TransitionLane6: Lane = /* */ 0b0000000000000000001000000000000; const TransitionLane7: Lane = /* */ 0b0000000000000000010000000000000; const TransitionLane8: Lane = /* */ 0b0000000000000000100000000000000; const TransitionLane9: Lane = /* */ 0b0000000000000001000000000000000; const TransitionLane10: Lane = /* */ 0b0000000000000010000000000000000; const TransitionLane11: Lane = /* */ 0b0000000000000100000000000000000; const TransitionLane12: Lane = /* */ 0b0000000000001000000000000000000; const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000; const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000; const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000; const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000; const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000; const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000; const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000; const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000; export const SomeRetryLane: Lane = RetryLane1; export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000; const NonIdleLanes: Lanes = /* */ 0b0000111111111111111111111111111; export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; export const IdleLane: Lane = /* */ 0b0010000000000000000000000000000; export const OffscreenLane: Lane = /* */ 0b0100000000000000000000000000000; export const DeferredLane: Lane = /* */ 0b1000000000000000000000000000000;
  1. 事件优先级

优先级依次降低,事件优先级也是包含在lane优先级中的。

// 离散事件优先级: input、点击事件等 export const DiscreteEventPriority: EventPriority = SyncLane; // 连续事件优先级:比如鼠标移动、拖拽d等 export const ContinuousEventPriority: EventPriority = InputContinuousLane; // 默认优先级:settimeout等 export const DefaultEventPriority: EventPriority = DefaultLane; // 闲置事件优先级 export const IdleEventPriority: EventPriority = IdleLane;

React在为每种事件都分配了不同的优先级。(下面为其中一小部分)

export function getEventPriority(domEventName: DOMEventName): EventPriority { switch (domEventName) { case 'cancel': case 'click': case 'popstate': case 'select': // .... case 'selectstart': return DiscreteEventPriority; case 'drag': case 'mousemove': case 'mouseout': // ... case 'mouseover': return ContinuousEventPriority; // ... default: return DefaultEventPriority; } }
  1. 调度优先级

优先级依次降低

// 立即优先级 export const ImmediatePriority = 1; // 用户阻塞优先级 export const UserBlockingPriority = 2; // 普通优先级 export const NormalPriority = 3; // 低优先级 export const LowPriority = 4; // 空闲优先级 export const IdlePriority = 5;

在调度前会通过update的优先级来确定调度的优先级,先把update的lane转成事件优先级,再把事件优先级转换成调度优先级,在调度时根据调度优先级确定任务的过期时间,从而确定任务的执行时机。

let schedulerPriorityLevel; switch (lanesToEventPriority(nextLanes)) { case DiscreteEventPriority: schedulerPriorityLevel = ImmediateSchedulerPriority; break; case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingSchedulerPriority; break; case DefaultEventPriority: schedulerPriorityLevel = NormalSchedulerPriority; break; case IdleEventPriority: schedulerPriorityLevel = IdleSchedulerPriority; break; default: schedulerPriorityLevel = NormalSchedulerPriority; break; } const newCallbackNode = scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root), );