UI交互的根本原因是各种事件,也就是说事件与更新有着直接的关系。不同事件产生的更新有不同的优先级,所以更新优先级的根源在于事件的优先级。一个更新的产生可以直接让React产生一个更新任务,最终这个任务被Scheduler调度。因此,在React中,事件被人为地划分了等级,最终目的是确定调度任务的优先级。因此,React从事件到调度都有一套优先级机制。本文将重点梳理事件优先级、更新优先级、任务优先级、调度优先级之间的转换关系。事件优先级:根据用户事件的交互紧急程度,划分优先级执行一个更新任务,这个任务持有的优先级调度优先级:Scheduler根据React更新任务生成一个调度任务。这个调度任务持有的前三个优先级属于React的优先级机制,第四个属于Scheduler,Scheduler的优先级机制内部有自己的优先级机制。虽然和React不同,但是层级的划分是基本一致的。让我们从事件优先级开始。优先级的出发点:事件优先级React根据事件的紧急程度将它们分为三个级别:离散事件(DiscreteEvent):点击、keydown、focusin等。这些事件的触发不是连续的,优先级为0。用户阻塞事件(UserBlockingEvent):拖拽、滚动、鼠标悬停等,特点是持续触发,阻塞渲染,优先级为1。持续事件(ContinuousEvent):canplay、error、audio标签的timeupdate和canplay,最高priority为2。EventPriorityMapDispatchEventPriority事件优先级在注册阶段确定。当向根注册一个事件时,会根据事件的类型创建不同优先级的事件监听器(listeners),并最终绑定到它上面。去根。letlistener=createEventListenerWrapperWithPriority(targetContainer,domEventName,eventSystemFlags,listenerPriority,);createEventListenerWrapperWithPriority函数的名字已经说明了它的作用。它会先根据事件的名称找到对应的事件优先级,然后根据优先级返回不同的事件监听函数。exportfunctioncreateEventListenerWrapperWithPriority(targetContainer:EventTarget,domEventName:DOMEventName,eventSystemFlags:EventSystemFlags,优先级?:EventPriority,):函数{consteventPriority=优先级===未定义?getEventPriorityForPluginSystem(domEventName):优先级;letlistenerWrapper;开关(eventDiscreteEventEvent){caseDiscreteEventEvent};break;caseUserBlockingEvent:listenerWrapper=dispatchUserBlockingUpdate;break;caseContinuousEvent:default:listenerWrapper=dispatchEvent;break;}returnlistenerWrapper.bind(null,domEventName,eventSystemFlags,targetContainerdis,);}最终绑定到根和事件监听器的事件监听器实际上是dispatchUserBlockingUpdate和dispatchEvent之一。它们做同样的事情,以各自的事件优先级执行真正的事件处理函数。例如:dispatchDiscreteEvent和dispatchUserBlockingUpdate最终会在UserBlockingEvent的事件级别执行事件处理函数。以一定的优先级执行事件处理函数,实际上是借助Scheduler中提供的runWithPriority函数实现的:eventSystemFlags,container,nativeEvent,),);...}这样做可以将事件优先级记录到Scheduler中,相当于告诉Scheduler:请帮我记录下当前事件派发的优先级,等待React在计算更新优先级的时候创建一个更新对象(即update),直接从你这里取就行了。functionunstable_runWithPriority(priorityLevel,eventHandler){switch(priorityLevel){caseImmediatePriority:caseUserBlockingPriority:caseNormalPriority:caseLowPriority:caseIdlePriority:break;default:priorityLevel=NormalPriority;}varpreviousPriorityLevel=currentPriorityLevel;//记录优先级到Scheduler内部的变量里currentPriorityLevel=priorityLevel;try{returneventHandler();}finally{currentPriorityLevel=previousPriorityLevel;}}更新优先级以setState为例,事件的执行会导致setState的执行,而setState本质上是调用enqueueSetState生成一个更新对象,将是计算此时更新优先级,即update.lane:constclassComponentUpdater={enqueueSetState(inst,payload,callback){...//根据事件优先级创建更新优先级constlane=requestUpdateLane(fiber,suspenseConfig);constupdate=createUpdate(eventTime,lane,suspenseConfig);update.payload=payload;enqueueUpdate(fiber,update);//开始调度scheduleUpdateOnFiber(fiber,lane,eventTime);...},};关注requestUpdateLane,它首先找出SchedulerPriority中记录的内容:schedulerPriority,然后计算updatepriority:lane,具体计算过程在findUpdateLane函数中,计算过程是一个从高位开始依次占用空闲位的操作tolow,具体代码在这里,这里是第一个不详细展开exportfunctionrequestUpdateLane(fiber:Fiber,suspenseConfig:SuspenseConfig|null,):Lane{...//根据记录的事件优先级,获取任务调度优先级constschedulerPriority=getCurrentPriorityLevel();letlane;if((executionContext&DiscreteEventContext)!==NoContext&&schedulerPriority===UserBlockingSchedulerPriority){//如果事件优先级为用户阻塞级别,则使用InputDiscreteLanePriority计算更新优先级事件优先级计算更新优先级lane=findUpdateLane(schedulerLanePriority,currentEventWipLanes);Priority:functionunstable_getCurrentPriorityLevel(){returncurrentPriorityLevel;}update对象创建后,表示页面需要更新,会调用scheduleUpdateOnFiber进入schedule,计算本次生成的update任务的任务优先级在实际调度之前。目的是对比已有任务的任务优先级,方便多任务调度决策。调度决策的逻辑在ensureRootIsScheduled函数中,这是一个非常重要的函数,控制着React任务进入Scheduler的大门。任务优先级更新将由React更新任务执行。任务优先级用于区分多个更新任务的紧急程度。它是根据更新优先级计算的。具有自己的更新优先级的更新也将由其各自的更新任务执行。经过优先级计算后,如果后者的任务优先级高于前者,则Scheduler将取消前者的任务调度;如果后者的任务优先级与前者的任务优先级相等,则后者不会导致前者被取消,前者的更新任务会被重用,两个相同优先级的更新会被收敛为一个任务;如果后一个任务的优先级低于前一个任务的优先级,则不会导致前一个任务被取消,而是在前一个更新完成后,使用Scheduler为后一个任务发起一次任务调度。这就是任务优先级存在的意义,保证对高优先级任务的及时响应,以及对相同优先级任务的调度收敛。任务优先级是在即将被调度时计算的。代码在ensureRootIsScheduled函数中://获取上面计算出的任务优先级constnewCallbackPriority=returnNextLanesPriority();...}通过调用getNextLanes计算本次更新应该处理的那批车道(nextLanes),getNextLanes会调用getHighestPriorityLanes计算任务优先级。任务优先级计算原理如下:更新优先级(updatelane),会合并到root.pendingLanes中,root.pendingLanes经过getNextLanes处理后,挑出那些应该处理的lane,传入getHighestPriorityLanes,并根据nextLanes找出这些lane的输出优先级作为任务优先级。functiongetHighestPriorityLanes(lanes:Lanes|Lane):Lanes{...//Thisistheprocessofcomparisonandassignment,onlytwoarereservedhereforabriefdescriptionconstinputDiscreteLanes=InputDiscreteLanes&lanes;if(inputDiscreteLanes!==NoLanes){return_highestLanePriority=InputDiscreteLanePriority;returninputDiscreteLanes;}if((lanes&InputContinuousHydrationLane)!==NoLanes){return_highestLanePriority=InputContinuousHydrationLanePriority;returnInputContinuousHydrationLane;}...returnlanes;}getHighestPriorityLanes的源码在这里,getNextLanes的源码在这里return_highestLanePriority就是任务优先级,它有如下这些值,thelargerthevalue,thehigherthepriority.Forthetimebeing,onlyunderstandtheroleoftaskpriority.exportconstSyncLanePriority:LanePriority=17;exportconstSyncBatchedLanePriority:LanePriority=16;constInputDiscreteHydrationLanePriority:LanePriority=15;exportconstInputDiscreteLanePriority:LanePriority=14;constInputContinuousHydrationLanePriority:LanePriority=13;exportconstInputContinuousLanePriority:LanePriority=12;constDefaultHydrationLanePriority:LanePriority=11;exportconstDefaultLanePriority:LanePriority=10;constTransitionShortHydrationLanePriority:LanePriority=9;exportconstTransitionShortLanePriority:LanePriority=8;constTransitionLongHydrationLanePriority:LanePriority=7;exportconstTransitionLongLanePriority:LanePriority=6;constRetryLanePriority:LanePriority=5;constSelectiveHydrationLanePriority:LanePriority=4;constIdleHydrationLanePriority:LanePriority=3;constIdleLanePriority:LanePriority=2;constOffscreenLanePriority:LanePriority=1;exportconstNoLanePriority:LanePriority=0;如果已经有更新任务,ensureRootIsScheduled会在获取到新任务的任务优先级后,将其与旧任务的任务优先级进行比较,从而做出决定是否需要重新发起调度决策。如果需要发起调度,则计算调度优先级。任务一旦被调度,就会进入Scheduler。在Scheduler中,会对任务进行打包,生成一个调度器。对于自己的任务,这个任务持有的优先级就是调度优先级。它有什么作用?在Scheduler中,过期任务队列和未过期任务队列分别用于管理其内部任务。过期任务队列中的任务按照过期时间排序,最早过期的排在最前面,这样可以最先处理。过期时间由调度优先级计算,不同的调度优先级对应不同的过期时间。调度优先级是根据任务优先级计算的。当ensureRootIsScheduled更新真正导致Scheduler发起调度时,会计算调度优先级。functionensureRootIsScheduled(root:FiberRoot,currentTime:number){...//根据任务优先级获取Scheduler的调度优先级constschedulerPriorityLevel=lanePriorityToSchedulerPriority(newCallbackPriority,);//计算出调度优先级后,让Scheduler调度ReactTasknewCallbackNode的更新=scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null,root),);...}lanePriorityToSchedulerPriority计算调度优先级的过程就是根据任务优先级找出对应的调度优先级。exportfunctionlanePriorityToSchedulerPriority(lanePriority:LanePriority,):ReactPriorityLevel{switch(lanePriority){caseSyncLanePriority:caseSyncBatchedLanePriority:returnImmediateSchedulerPriority;caseInputDiscreteHydrationLanePriority:caseInputDiscreteLanePriority:caseInputContinuousHydrationLanePriority:caseInputContinuousLanePriority:returnUserBlockingSchedulerPriority;caseDefaultHydrationLanePriority:caseDefaultLanePriority:caseTransitionShortHydrationLanePriority:caseTransitionShortLanePriority:caseTransitionLongHydrationLanePriority:caseTransitionLongLanePriority:caseSelectiveHydrationLanePriority:caseRetryLanePriority:returnNormalSchedulerPriority;caseIdleHydrationLanePriority:caseIdleLanePriority:caseOffscreenLanePriority:returnIdleSchedulerPriority;caseNoLanePriority:returnNoSchedulerPriority;default:invariant(false,'Invalidupdatepriority:%s.ThisisabuginReact.',lanePriority,);}}总结本文提到四种优先级和更新优先级:、任务priority,和schedulingpriority,它们之间是递进的关系。事件优先级由事件本身决定,更新优先级由事件计算,然后放入root.pendingLanes,任务优先级来自root.pendingLanes中最紧急的lanes对应的优先级,调度优先级为根据任务优先级获得。多个优先级联动,确保高质量任务的优先执行。
