React采用了全新的Fiber架构,将递归一次查找所有变化,一次更新真实DOM的过程改为时间分片,先分成小的异步任务空闲时发现变化,最后一口气更新DOM。这里需要用到一个调度器,在浏览器空闲的时候做这些异步的小任务。做这个调度工作的Scheduler在React中被称为Scheduler模块。其实浏览器提供了一个requestIdleCallback方法,这样我们就可以在浏览器空闲的时候调用传入的回调函数。但是由于兼容性不好,可能给定的优先级太低,在渲染帧中执行。因此React实现了requestIdleCallback的替代方案,即Scheduler。它的底层是基于MessageChannel的。为什么是MessageChannel?之所以选择MessageChannel,是因为它必须是异步的宏任务,因为宏任务会在下一个事件循环中执行,不会阻塞当前页面的更新。MessageChannel是一个宏任务。没有选择常用的setTimeout,因为MessageChannel可以快速执行,在0-1ms内触发。和setTimeout一样,即使超时设置为0,仍然需要4-5ms。同时,MessageChannel可以完成更多的任务。如果浏览器不支持MessageChannel,还是要降级为setTimeout。事实上,如果存在setImmediate,则会优先使用setImmediate,但它只存在于少数环境中(如低版本的IE、Node.js)。总编辑在packages/scheduler/src/forks/Scheduler.js中实现的://捕获对本机API的本地引用,以防polyfill覆盖它们。constlocalSetTimeout=typeofsetTimeout==='function'?setTimeout:null;constlocalClearTimeout=typeofclearTimeout==='function'?clearTimeout:null;constlocalSetImmediate=typeofsetImmediate!=='undefined'?立即设置:空;//IE和Node.js+jsdom/*****异步选择策略*****///【1】优先使用setImmediateif(typeoflocalSetImmediate==='function'){//Node.jsandoldIE。schedulePerformWorkUntilDeadline=(){localSetImmediate(performWorkUntilDeadline);};}//【2】然后是MessageChannelelseif(typeofMessageChannel!=='undefined'){//DOM和Worker环境。//我们更喜欢MessageChannel,因为4mssetTimeout钳位。constchannel=newMessageChannel();constport=channel.port2;channel.port1.onmessage=performWorkUntilDeadline;schedulePerformWorkUntilDeadline=(){port.postMessage(空);};}//【3】最后一个是setTimeout(pocketbottom)else{//我们应该只在非浏览器环境下回退到这里。schedulePerformWorkUntilDeadline=(){localSetTimeout(performWorkUntilDeadline,0);};}其他,并没有选择使用requestAnimationFrame,因为它的机制比较特殊,是在更新页面之前执行的,但是没有指定更新页面的时机,执行时机并不稳定。底层异步循环requestHostCallback方法用于请求主机(指浏览器)执行函数。该方法会将传入的函数保存到scheduledHostCallback中,然后调用schedulePerformWorkUntilDeadline方法。schedulePerformWorkUntilDeadline方法一旦被调用,就无法停止。它会异步调用performWorkUntilDeadline,performWorkUntilDeadline又回调到schedulePerformWorkUntilDeadline,最终实现performWorkUntilDeadline的连续异步循环执行。//请求主机(指浏览器)执行函数functionrequestHostCallback(callback){scheduledHostCallback=callback;如果(!isMessageLoopRunning){isMessageLoopRunning=true;schedulePerformWorkUntilDeadline();}}isMessageLoopRunning是一个标志,指示循环是否正在运行。防止schedulePerformWorkUntilDeadline同时被多次调用。React会调度workLoopSync/workLoopConcurrent。React项目启动后,我们执行更新操作并调用ensureRootIsScheduled方法。functionensureRootIsScheduled(root,currentTime){//最高优先级if(newCallbackPriority===SyncLane){//特殊情况:SyncReact回调被安排在一个特殊的//内部队列上if(root.tag===LegacyRoot){//LegacyMode,即ReactDOM.render()scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null,root))启用的同步模式;}else{scheduleSyncCallback(performSyncWorkOnRoot.bind(null,root));}//立即执行优先级,清除需要同步执行的任务scheduleCallback(ImmediateSchedulerPriority,flushSyncCallbacks);}else{//初始化schedulerPriorityLevel并计算Scheduler支持的优先级值letschedulerPriorityLevel;//...scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.root)(,//并发模式);}}这个方法有很多分支,最终会根据条件调用:performSyncWorkOnRoot(立即执行)performConcurrentWorkOnRoot(并发执行,会使用调度器的scheduleCallback进行异步调用)performSyncWorkOnRoot最终会执行重要的workLoopSync方法://调用链接://performSyncWorkOnRoot->>;renderRootSync->workLoopSync函数workLoopSync(){while(workInProgress!==null){performUnitOfWork(workInProgress);}}workInProgress表示一个需要处理的FiberNode。performUnitOfWork方法用于处理一个workInProgress,执行一个reconciliation操作,计算一个新的fiberNode。同样,performConcurrentWorkOnRoot最终会执行重要的workLoopConcurrent方法。//调用链接://performConcurrentWorkOnRoot->performConcurrentWorkOnRoot->renderRootConcurrentfunctionworkLoopConcurrent(){while(workInProgress!==null&&!shouldYield()){performUnitOfWork(workInProgress);}}和workLoopSync很像,但是循环条件多了一个来自Scheduler的shouldYield()来决定是否把进程yield给浏览器,这样可以打断Fiber的reconciliation阶段,实现时间分片.scheduleCallback上的workLoopSync和workLoopConcurrent都是通过scheduleCallback进行调度的。scheduleCallback方法传入优先级priorityLevel,需要指定的回调函数callback,以及一个可选的选项options。scheduleCallback的实现如下(简化):变种开始时间;if(options?.delay){startTime=currentTime+options.delay;}//有效期Duration,根据优先级设置。变量超时;//...//计算过期时间varexpirationTime=startTime+timeout;//创建一个任务varnewTask={id:taskIdCounter++,callback,//这是任务本身priorityLevel,startTime,expirationTime,sortIndex:-1,};//表示新任务是一个添加了option.delay的任务,需要延迟执行//我们会把它放到未过期队列(timerQueue)中if(startTime>currentTime){newTask.sortIndex=startTime;推送(timerQueue,newTask);//没有任务需要逾期,优先级最高的没有逾期的任务就是这个新任务if(peek(taskQueue)===null&&newTask===peek(timerQueue)){//然后,使用setTimeout延迟options.delay来执行handleTimeoutrequestHostTimeout(handleTimeout,startTime-currentTime);}}//立即执行的任务,加入过期队列(taskQueue)else{newTask.sortIndex=expirationTime;推(任务队列,新任务);//如果需要,安排主机回调。如果我们已经在执行工作,//等到下次我们让步。如果(!isHostCallbackScheduled&&!isPerformingWork){isHostCallbackScheduled=tr等;requestHostCallback(flushWork);}}}push/peek/pop这些是调度器提供的操作优先级队列的操作方法。优先级队列的底层实现是一个小顶堆,实现原理就不说了。我们只需要记住优先级队列的特点:即当队列出队时,优先级最高的任务会被拿走。在调度器中,sortIndex最小的任务具有最高的优先级。push(queue,task)表示加入队列并添加新任务;peek(queue)表示获得最高优先级(未出队);pop(queue)表示出列最高优先级的任务。taskQueue是一个过期的任务队列,需要快速执行。新生成的任务(没有设置options.delay)会被放入taskQueue并与expirationTime作为优先级进行比较(sortIndex)。timerQueue是一个还没有过期的任务队列,相对于startTime为优先级。逾期则取出,放入taskQueue。handleTimeout//如果没有逾期任务,且优先级最高的非逾期任务是这个新任务//延迟执行handleTimeoutif(peek(taskQueue)===null&&newTask===peek(timerQueue)){requestHostTimeout(handleTimeout,startTime-currentTime);}requestHostTimeout其实是对setTimeout定时器的简单封装,在newTask超时时(startTime-currentTime之后)执行handleTimeout。函数handleTimeout(currentTime){isHostTimeoutScheduled=false;advanceTimers(当前时间);//更新timerQueue和taskQueueif(!isHostCallbackScheduled){if(peek(taskQueue)!==null){//有超时任务要执行isHostCallbackScheduled=true;requestHostCallback(flushWork);//清除任务队列任务}else{//没有逾期任务constfirstTimer=peek(timerQueue);if(firstTimer!==null){//但是有未逾期的任务,使用setTimeout稍后调用自己requestHostTimeout(handleTimeout,firstTimer.startTime-currentTime);}}}}handleTimeout下会调用advanceTimers方法,将timerTask中的超时任务按照当前时间移到taskQueue中。(advanceTimers方法会在多处调用,移动一下,会更健康。)移动后查看taskQueue中是否有待完成的任务,如果有则调用flushWork清除taskQueue中的任务。如果没有,检查是否有任务没有超时,超时时使用定时器递归执行handleTimeout。workLoopflushWork调用workLoop。flushWork还需要做一些额外的操作来修改模块文件的变量。functionflushWork(hasTimeRemaining,initialTime){//...returnworkLoop(hasTimeRemaining,initialTime);}workLoop会不断从taskQueue中取任务执行。它的核心逻辑是:functionworkLoop(hasTimeRemaining,initialTime){//更新taskQueue,取出一个taskletcurrentTime=initialTime;advanceTimers(当前时间);当前任务=偷看(任务队列);while(currentTask!==null){if(currentTask.expirationTime>currentTime&&(!hasTimeRemaining||shouldYieldToHost())){//这个currentTask还没有过期,我们已经到了截止日期。休息;}//执行任务constcallback=currentTask.callback;打回来();//更新taskQueue,取出一个taskcurrentTime=getCurrentTime();advanceTimers(当前时间);当前任务=偷看(任务队列);}returncurrentTask!==null;}上面的shouldYieldToHost循环不会一直执行到currentTask为null为止,必要时还是会跳出。我们通过shouldYieldToHost方法判断是否跳出。另外,Fiber异步更新的workLoopConcurrent方法中使用的shouldYield其实就是这个shouldYieldToHost。shouldYieldToHost核心实现:constframeYieldMs=5;varframeInterval=frameYieldMs;函数shouldYieldToHost(){vartimeElapsed=getCurrentTime()-startTime;//如果流逝时间小于5ms,则无需放弃流程if(timeElapsed
