react是一个通用型开源前端框架,在前端各(hua)种(li)优(hu)秀(shao)的前端界面构建库中尤为出名。对此本着让开发时甩锅bug的理由更有说服力,做为新一代苦逼的打工仔我决定开启react源码阅读之路。
打开react代码库后,我们会发现react的主要源码按照功能划分的各个模块主要放在packages文件夹下,其中做为react核心模块之一的scheduler负责react的各种任务调度便是本篇的主角。
本篇研究的react是17.0.2版本,相关的项目--> 这里,代码路径:react/packages/scheduler/src/forks/Scheduler.js
react中的ui渲染、数据操作、事件处理等都会生成对应的处理任务通过 unstable_scheduleCallback(priorityLevel, callback, options)
进入react任务调度,其中 priorityLevel
为调度的优先级,目前react任务优先级由高到低主要有ImmediatePriority
(立即执行)、 UserBlockingPriority
(用户阻塞)、NormalPriority
(普通)、 LowPriority
(低优先级)、IdlePriority
(空闲);callback
为任务处理函数,options
为控制项,目前只支持{delay: number;}
标明该任务是延时执行任务。
Scheduler中把任务分成两类:一种为普通任务,另一种为延时触发任务。通过unstable_scheduleCallback
将任务分别放入对应的( taskQueue
、timerQueue
)任务堆中去。
taskQueue和timerQueue都属于Scheduler维护的最小堆结构,其中taskQueue以任务过期时间
expirationTime
(在unstable_scheduleCallback
中生成,等于当前时间priorityLevel
对应的时间)为堆排序依据,只有在taskQueue中的任务才会被执行,timerQueue以任务触发时间startTime
(在unstable_scheduleCallback
中生成,等于当前时间options.delay
对应的时间)为堆排序依据,该堆存在任务时会对该堆顶任务进行检测,当当前时大于等于堆定任务的触发时间时,该任务会被移入taskQueue中。
在unstable_scheduleCallback
的最后当taskQueue中存在任务时会执行requestHostCallback
把taskQueue任务循环推入下一个js系统事件循环中的宏任务中执行。
在下一个js系统任务循环的宏任务开始时,Scheduler会把当前时间记录进startTime全局变量中去,然后执行workLoop
进入Scheduler任务循环,workLoop
会不断的从taskQueue堆顶中取出任务执行,直到taskQueue中没有任务或者taskQueue的堆顶任务过期时间大于当前时间(任务未过期)但是任务运行时间(当前时间 - 全局startTime)大于允许帧间隔frameInterval
时间,停止循环。最后判断如果taskQueue中存在任务,则调用requestHostCallback
,让系统在下一个js宏任务继续执行taskQueue中的任务。
为了确保页面渲染不卡顿,至少每秒渲染30帧,js是单线程语言,所以在每一帧时间内要js计算和浏览器渲染。在
workLoop
中,通过frameInterval
控制js计算时间,从而把复杂的渲染任务分割成多个帧进行渲染。
react任务调度流程图如下:
总结:
- Scheduler通过维护最小堆使得timerQueue堆顶任务最先开始进入taskQueue、taskQueue堆顶任务最先执行。
- Scheduler会不断的检测timerQueue中的延时执行任务的开始时间超时从而将任务转入taskQueue中。
- Scheduler在每个宏任务中会执行所有过期任务,如果有剩余时间(当前时间 - 全局startTime < 帧间隔时间)则再执行没有过期的任务。