当前位置: 首页 > Web前端 > JavaScript

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

时间:2023-03-26 23:51:08 JavaScript

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