当前位置: 首页 > 科技观察

100行代码实现React核心调度功能

时间:2023-03-15 01:06:14 科技观察

大家好,我是Kason。想必大家都知道React有一个基于Fiber架构的调度系统。该调度系统的基本功能包括:更新具有不同的优先级。一次更新可能涉及多个组件的渲染,这些渲染可能会分配给多个宏任务执行(即时间分片)。高优先级更新会中断正在进行的低优先级更新本文将用100行代码来实现这个调度系统,让你快速了解React的调度原理。我知道你不喜欢看大段代码,所以本文将以图片+代码片段的形式进行讲解。文末有完整的在线Demo,大家可以自己玩玩。让我们做好准备!我们用work数据结构来表示一个job,work.count表示这个job需要重复的次数。demo中要重复的是“执行insertItem方法插入到页面中”:constinertItem=(content:string)=>{constele=document.createElement('span');ele.innerText=`${content}`;contentBox.appendChild(ele);};因此,对于下面的工作:constwork1={count:100}表示:执行100insertItems向页面插入100个item。work可以类比为React的一次更新,work.count就像这次更新要渲染的组件数量。所以Demo是类比React的更新过程来实现第一版的调度系统。流程如图:包括三个步骤:将work插入workList队列(用于保存所有work)schedule方法从workList中取出work,传递给perform方法执行完work的所有工作,重复步骤2。代码如下://保存所有工作的队列constworkList:work[]=[];//调度函数schedule(){//从队尾获取一个工作constcurWork=workList.pop();if(curWork){perform(curWork);}}//执行函数perform(work:Work){while(work.count){work.count--;insertItem();}schedule();}绑定按钮点击交互,最基本的调度系统就完成了:button.onclick=()=>{workList.unshift({count:100})schedule();}点击按钮插入100个。React类比是:点击按钮,触发同步更新,渲染100个组件。接下来,我们将其转化为异步。SchedulerReact内部使用Scheduler完成异步调度。调度程序是一个独立的包。所以我们可以利用他来改造我们的Demo。Scheduler预设了5个优先级,从上到下递减:ImmediatePriority,同步优先级最高,UserBlockingPriority,NormalPriority,LowPriority,IdlePriority,优先级最低。scheduleCallback方法接收优先级和回调函数fn,用于调度fn://will回调函数fn以LowPriority优先级调度scheduleCallback(LowPriority,fn)。在Scheduler内部,执行scheduleCallback后,会生成一个task的数据结构:consttask1={expiration:startTime+timeout,callback:fn}task1.expiration表示task1的过期时间,Scheduler会优先执行过期的task。打回来。expiration中的startTime为当前开始时间,不同优先级的超时时间不同。比如ImmediatePriority的超时时间为-1,因为:startTime-1{returnw1.priority-w2.priority;})[0];从流程图可以看出改造后流程的变化,Scheduler不再直接执行perform,而是通过执行scheduleCallback调度perform.bind(null,work)。即当满足一定条件时,会产生一个新的任务:constsomeTask={callback:perform.bind(null,work),expiration:xxx}同时work的工作也可以中断。改造前,perform会同步执行work中的所有work:while(work.count){work.count--;insertItem();}改造后,work的执行过程随时可能被打断:while(!needYield()&&work.count){work.count--;insertItem();}needYield方法实现(什么时候会中断)high-的例子请参考文末在线Demo优先级中断和低优先级例子看一个高优先级中断低优先级例子:插入一个低优先级的工作,属性如下constwork1={count:100,priority:LowPriority}execution),在执行80次work的时候,突然插入一个高优先级的work,此时:constwork1={//work1已经执行了80次work,还有20次要执行count:20,priority:LowPriority}//新插入的高优先级工作constwork2={count:100,priority:ImmediatePriority}work1work中断,继续安排。由于work2的优先级较高,所以会进入work2对应的perform,执行100个job。work2执行完后,继续调度执行work1剩余的20个作业。在这个例子中,我们需要区分“中断”的两个概念:在第3步中,work1执行的工作被中断了。这是微观上的“中断”。由于work1被中断,调度继续进行。下一个要执行的作业是更高优先级的work2。work2的到来导致work1中断。这就是宏观上的“中断”。之所以区分“宏/微”,是因为“微中断”并不一定就是“宏中断”。例如:work1因为时间片用完而中断。如果没有其他更高质量的作品与他竞争档期,下一场演出仍将是作品1。这种情况下,微观上有多次中断,但在宏观上,仍然在执行同一个工作。这就是“时间分片”的原理。调度系统的实现原理下面是完整的调度系统实现原理:根据流程图:总结本文是一个React调度系统的简单实现,主要包括两个阶段:scheduleperform如果你有兴趣代码的具体实现,以下是完整的demo地址。参考文献[1]调度器:https://github.com/facebook/react/tree/main/packages/scheduler[2]完整Demo地址:https://codesandbox.io/s/xenodochial-alex-db74g?file=/src/index.ts