react源码--任务调度系统

404 阅读3分钟

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 将任务分别放入对应的( taskQueuetimerQueue )任务堆中去。

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任务调度流程图如下:

react任务调度流程图.jpg

总结:

  1. Scheduler通过维护最小堆使得timerQueue堆顶任务最先开始进入taskQueue、taskQueue堆顶任务最先执行。
  2. Scheduler会不断的检测timerQueue中的延时执行任务的开始时间超时从而将任务转入taskQueue中。
  3. Scheduler在每个宏任务中会执行所有过期任务,如果有剩余时间(当前时间 - 全局startTime < 帧间隔时间)则再执行没有过期的任务。