大家好,我是Kason。调度器(scheduler)[1]是React的重要组成部分。同时,它也是一个独立的包。任何“连续且可中断”的进程都可以被Scheduler调度,例如:constwork={count:100};functiondoWork(work){work.count--;console.log('dowork!')}work满足两个条件:这项工作是连续的。一共需要执行100次,每次执行都可以中断调用doWork的工作。中断恢复后,中断前的work.count会继续执行,只要满足这两个条件的work就可以被Scheduler调度。调度完成后,Scheduler会在内部生成相应的任务,并在正确的时间执行task.callback:consttask1={//过期时间等于当前时间+优先级对应时间expirationTime:currentTime+priority,callback:doWork.bind(null,work)}本文将讲解Scheduler的实现原理。我知道你不喜欢阅读大段代码,所以本文没有一行代码。文末有Scheduler的源码地址。如果您有兴趣,可以查看一下。开征~工作流程概述Scheduler的工作原理如下图所示,接下来会详细解释:Scheduler中有两个容易混淆的概念:1.delaydelay意思是“任务需要延迟执行的时间””。配置了delay的任务会先进入timerQueue。当延迟对应的时间到期时,任务会被转移到taskQueue中。2.expirationTimeexpirationTime表示“任务过期时间”。并不是所有的任务都会配置延时,没有延时的任务会直接进入taskQueue。这就导致了taskQueue中有多个任务的可能性。如何决定先执行哪个task.callback?Scheduler以task.expirationTime作为排序依据,值越小,优先级越高。如果task.expirationTime小于当前时间,不仅优先级最高,而且task.callback的执行也不会被打断。总结一下task的几种情况:配置了delay且delayhasnotexpired:task不得执行配置了delay且已过期的task,或者配置了不带delay的task且task.expirationTime未过期:根据task.expirationTime排序后,按顺序执行任务task.expirationTimeexpired:优先级最高,且同步,不可中断的工作流详解将流程概览图替换成Scheduler中的具体方法后,完整的工作流如下:1.执行Scheduler。scheduleCallback根据“是否传递delay参数”来生成任务,生成的任务会进入timerQueue或taskQueue。2.当timerQueue中第一个任务的延迟时间到期时,执行advanceTimers将timerQueue中的“过期任务”移动到taskQueue中。timerQueue和taskQueue的数据结构都是小顶堆实现的优先级队列。3、接下来执行requestHostCallback方法,会执行新宏任务中的workLoop方法。“在宏任务中执行回调”的方法有很多。Scheduler在浏览器环境中默认使用MessageChannel。如果不支持MessageChannel,则会降级为setTimeout。Node或旧版本的IE将使用setImmediate。4、workLoop方法会循环消费taskQueue中的任务(即执行task.callback),直到满足以下条件之一,中断循环:taskQueue和时间片中没有任务筋疲力尽。5.循环中断后,如果taskQueue不为空,则转步骤3。如果timerQueue不为空,则转步骤2。综上所述,Scheduler完整的执行过程包括两个循环:taskQueue的生产(从timerQueue移入或执行scheduleCallback的生成)到消费的过程(即灰度)图中的部分),这是一个异步循环taskQueue的具体消费过程(也就是workLoop方法的执行)。这是一个同步循环。如果想了解《如何在React中使用Scheduler》,可以参考100行代码实现React的核心调度功能。参考[1]Scheduler(调度器):https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js
