ITPub博客

首页 > 应用开发 > IT综合 > web前端培训:React 核心调度功能的实现

web前端培训:React 核心调度功能的实现

IT综合 作者:IT小尚 时间:2022-03-07 11:42:11 0 删除 编辑

想必大家都知道 React有一套基于 Fiber架构的调度系统。这套调度系统的基本功能包括:

  • 更新有不同优先级

  • 一次更新可能涉及多个组件的 render,这些 render可能分配到多个 宏任务中执行(即 时间切片)

  • 高优先级更新会打断进行中的 低优先级更新

本文会用100行代码实现这套调度系统,让你快速了解 React的调度原理。

我知道你不喜欢看大段的代码,所以本文会以 图+ 代码片段的形式讲解。

文末有完整的 在线Demo,你可以自己上手玩玩。

开整!

准备工作

我们用 work这一数据结构代表一份工作, work.count代表这份工作要重复做某件事的次数_ 前端培训。

在 Demo中要重复做的事是“执行 insertItem方法,向页面插入 <span/>”:

const insertItem = (content: string) => {

const ele = document.createElement('span');

ele.innerText = `${content}`;

contentBox.appendChild(ele);

};

所以,对于如下 work:

const work1 = {

count: 100

}

代表:执行100次 insertItem向页面插入100个 <span/>。

work可以类比React的一次更新,work.count类比这次更新要render的组件数量。 【关注尚硅谷,轻松学IT】所以Demo是对React更新流程的类比

来实现第一版的调度系统,流程如图:

 

包括三步:

  1. 向 workList队列(用于保存所有 work)插入 work
  2. schedule方法从 workList中取出 work,传递给 perform
  3. perform方法执行完 work的所有工作后重复 步骤2

代码如下:

// 保存所有work的队列

const workList: work[] = [];

// 调度

function schedule() {

// 从队列尾取一个work

const curWork = workList.pop();

if (curWork) {

perform(curWork);

}

}

// 执行

function perform(work: Work) {

while (work.count) {

work.count--;

insertItem();

}

schedule();

}

为按钮绑定点击交互,最基本的调度系统就完成了:

button.=> {

workList.unshift({

count: 100

})

schedule();

}

点击 button就能插入100个 <span/>。

用React类比就是:点击button,触发同步更新,100个组件render

接下来我们将其改造成异步的。

Scheduler

React内部使用Scheduler完成异步调度。

Scheduler是独立的包。所以可以用他改造我们的 Demo。

Scheduler预置了5种优先级,从上往下优先级降低:

  • ImmediatePriority,最高的同步优先级

  • UserBlockingPriority

  • NormalPriority

  • LowPriority

  • IdlePriority,最低优先级

scheduleCallback方法接收 优先级与回调函数 fn,用于调度 fn:

// 将回调函数fn以LowPriority优先级调度

scheduleCallback(LowPriority, fn)

在 Scheduler内部,执行 scheduleCallback后会生成 task这一数据结构:

const task1 = {

expiration: startTime + timeout,

callback: fn

}

task1.expiration代表 task1的过期时间, Scheduler会优先执行过期的 task.callback。

expiration中 startTime为当前开始时间, 【关注尚硅谷,轻松学IT】不同优先级的 timeout不同_ web前端培训。

比如, ImmediatePriority的 timeout为-1,由于:

startTime - 1 < startTime

所以 ImmediatePriority会立刻过期, callback立刻执行。

而 IdlePriority对应 timeout为1073741823(最大的31位带符号整型),其 callback需要非常长时间才会执行。

callback会在新的 宏任务中执行,这就是 Scheduler调度的原理。

用Scheduler改造Demo

改造后的流程如图:

 

改造前, work直接从 workList队列尾取出:

// 改造前

const curWork = workList.pop();

改造后, work可以拥有不同 优先级,通过 priority字段表示。

比如,如下 work代表 「以NormalPriority优先级插入100个<span/>」:

const work1 = {

count: 100,

priority: NormalPriority

}

改造后每次都使用最高优先级的 work:

// 改造后

// 对workList排序后取priority值最小的(值越小,优先级越高)

const curWork = workList.sort((w1, w2) => {

return w1.priority - w2.priority;

})[0];

改造后流程的变化

由流程图可知, Scheduler不再直接执行 perform,而是通过执行 scheduleCallback调度 perform.bind(null, work)。

即,满足一定条件的情况下,生成新 task:

const someTask = {

callback: perform.bind(null, work),

expiration: xxx

}

同时, work的工作也是可中断的。在改造前, perform会同步执行完 work中的所有工作:

while (work.count) {

work.count--;

insertItem();

}

改造后, work的执行流程随时可能中断:

while (!needYield() && work.count) {

work.count--;

insertItem();

}

高优先级打断低优先级的例子

举例来看一个 高优先级打断 低优先级的例子:

  1. 插入一个低优先级 work,属性如下

const work1 = {

count: 100,

priority: LowPriority

}

  1. 经历 schedule(调度), perform(执行),在执行了80次工作时,突然插入一个高优先级 work,此时:

const work1 = {

// work1已经执行了80次工作,还差20次执行完

count: 20,

priority: LowPriority

}

// 新插入的高优先级work

const work2 = {

count: 100,

priority: ImmediatePriority

}

  1. work1工作中断,继续 schedule。由于 work2优先级更高,会进入 work2对应 perform,执行100次工作
  2. work2执行完后,继续 schedule,执行 work1剩余的20次工作

在这个例子中,我们需要区分2个 「打断」的概念:

  1. 在步骤3中, work1执行的工作被打断。这是微观角度的 「打断」
  2. 由于 work1被打断,所以继续 schedule。下一个执行工作的是更高优的 work2。 work2的到来导致 work1被打断,这是宏观角度的 「打断」

之所以要区分 「宏/微观」,是因为 「微观的打断」不一定意味着 「宏观的打断」。

比如: work1由于时间切片用尽,被打断。没有其他更高优的 work与他竞争 schedule的话,下一次 perform还是 work1。

这种情况下微观下多次打断,但是宏观来看,还是同一个 work在执行。这就是 「时间切片」的原理。

调度系统的实现原理

以下是调度系统的完整实现原理:

 

对照流程图来看:

 

文章来源于前端开发


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70010293/viewspace-2865546/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论
IT学习分享www.atguigu.com

注册时间:2021-11-19

  • 博文量
    328
  • 访问量
    156811