没有人比 React 更懂喂猫

1,840 阅读7分钟

一起用代码吸猫!本文正在参与【喵星人征文活动】

背景

话说这天你在家里接到女朋友的电话,聊到最后,“亲爱的,记得喂猫哦~”

正说着,只见两只小猫从阳台追到客厅,空中纷飞这散落的猫毛。你正准备去抓住他们,突然小腿传来一阵毛茸茸的感觉,低头一看,布偶小公主正抬头对着你咪咪叫,你知道它饿了,不得不先去准备猫粮,撕开猫粮的瞬间余光瞥到稳重的大橘压在你新买的 mac 上。

面对如此情景你心想,这是养了一群祖宗呀,要是有个方法能管住他们就好了。

React从入门.jpeg

翻开了《React 从入门到入土》,目光停在了《调度原理》章节上

随着手指的翻阅一幅完整的画面从脑海中出现开来

如果让 React 来喂猫

猫,作为一种黏人的小动物,和女儿并称为男生上辈子的小情人,

轻轻咪一声就可以抹去我们一天的疲惫。

那么养猫呢必然离不开喂猫,也就是我们今天的主题,

1、批量任务

假如说我们养了好多好多猫咪,考虑到如果每隔几分钟就有猫猫饿了跑过来要吃的,那我们要反复地 「开猫粮、倒猫粮、封猫粮」,如此重复的方式非常低效。

而猫猫都特别馋,一听到开猫粮的声音会全部凑过来,就算不怎么饿也要尝一口。

针对这一特点,每次有猫猫来讨粮时,我们可以用开猫粮的声音把他们都吸引过来,然后一次性地投喂。

开饭喽.jpg

于是乎,我们把这种方式称为 批量任务

批量任务:将每只猫猫的投喂计划排入队列,并通知React接下来处理队列中所有的计划

2、任务的优先级与运算

另外喂猫的时候呢,我们发现猫主子们又争又抢,甚至不惜站起来化身 人类祖先🐒 顺着裤腿往上爬,这时候我们肯定会忙不过来,所以我们有自己的一套喂猫顺序。

比如,

  • 首先投喂闹腾的小猫,如果投喂不及时他会一直闹,搞不好还会拆家
  • 其次呢喂乖巧黏人的猫,猫猫这么乖你舍得让他一直眼巴巴地挨饿嘛
  • 最后喂最外围看热闹的猫,因为他们可能不怎么饿,只是要尝尝这袋猫粮好不好吃

当然了,这只是一个理想状态,真实情况下还要根据猫猫的 饥饿程度宠爱程度 等多方面原因来决定投喂顺序,这些因素都可以看作是 任务优先级,也就是投喂的先后顺序。

多个优先级在我们的大脑中会经过衡量,最终得决定先喂哪一只,这个思考的过程就是 优先级运算

谁想那么多.jpeg

任务优先级:猫猫投喂顺序的考虑点,比如饥饿程度、乖巧程度、饭量等等
优先级运算:综合考虑上述优先级,最终决定先投喂哪一只猫

3、超时时间

如果猫猫等太久而一直吃不到猫粮就会开始闹腾,所以我们也会酌情先投喂那些等了很久的猫猫,猫猫的这个忍耐时间呢叫做 超时时间 ,即:超过这个时间再不吃饭就该抗议了。

超时时间:最晚投喂时间

4、任务中断与任务恢复

另外在喂猫的过程中呢,

可能会穿插进来比较重要的事情,比如女朋友的电话还没挂断,突然问道“我和你妈同时掉进水里你救谁”,这时候我们得思考一会儿对不对,

毕竟猫和女朋友哪个重要我们还是很清楚的

哎哎哎咱放下刀再说.jpeg

所以万一有更重要的事情出现,我们会暂停现在喂猫的动作,先处理更重要的事情,等忙完再继续喂猫,

从暂停喂猫,到回来继续喂猫这个过程分别是 任务中断任务恢复

任务中断:有更重要的事情进入,暂停喂猫的动作
任务恢复:重要的事情处理完毕,回来继续喂刚才那只猫

5、协调

最后考虑一点,我们每次并不会给猫猫投喂过多的猫粮,因为这会让他们撑着。所以我们会控制好投喂的量。怎么控制呢,当然是和猫猫上次进食的量做对比呐,比如刚刚吃了一大碗猫粮,那这次就要少吃一点或者只给吃一粒。

20211124213605.jpg

这个对比的过程我们称为 协调,或者说叫 diff

同样这个过程几乎也是一瞬间完成的,不会占用过多时间。

协调:根据这只猫上次吃的量决定这次吃多少

6、这就是 React 调度

此时的你被一群猫围着,手机夹在肩膀和脸之间,一边跟女朋友打着电话,一边把桌上捣蛋的小猫抱到地上,当然回来后还忘不了继续手上喂猫的动作,

看着忙碌的你,趴在沙发上的小白猫梳理着毛发,慵懒地神了个懒腰,不禁发出感叹:这么好的男人,就像是。。。像是 React 调度 一样

为什么是 React 调度

1、把主人比作 JS 执行引擎,

2、那么每只猫猫的投喂就是一个个的渲染任务,

3、女朋友是用户的 IO 行为

接下来将现实和React进行对照:

现实:女朋友打电话让你记得喂猫

React:用户的 IO 行为触发setState,从而引发组件渲染

现实:你打开猫粮弄出很大的动静,猫猫听到声音全都跑过来

React:我们不会说触发更新行为时直接去更新,而是批量执行多个更新任务

现实:猫猫一下子全都跑过来,但女朋友还有事情要说

React:在组件渲染之前用户还在继续 IO 操作,但 js 的执行引擎是单线程的,一时间无法兼顾

现实:继续和女朋友在电话说着,中间空了就继续喂猫

React:在当前的这一帧里优先处理用户 IO 行为,剩余时间用来渲染组件

现实:女朋友突然问“我和你妈掉进水里了你先救谁”,这时候你得好好思考一下再回答

React:用户的 IO 行为占用了过多时间,当前这一帧没有剩余时间再渲染组件了,所以先暂停渲染任务

现实:丛容得回答完问题,女朋友很开心,你继续喂猫

React:用户的 IO 行为处理完毕,当前帧有剩余时间,从上次暂停的地方继续执行渲染任务

现实:整个喂猫的过程中你衡量猫猫的饥饿程度还有乖巧程度等多个原因来控制喂猫的顺序

React:组件渲染有优先级,多个优先级经过运算,决定最终的渲染次序

现实:猫猫饿急了大声地咪咪叫,于是你先喂给他

React:组件渲染也超时时间,超过这个时间要立即渲染

现实:喂给每只猫的猫粮不同,这取决于他刚刚吃了多少

React:组件渲染并不是整个重新渲染,而是根据 diff(调度)的结果决定

prefect.jpeg

写在最后

通过本文我们知道了,React将调度的粒度细化到浏览器的每一帧。

当我们触发更新后 React 并不会立即执行,而是创建一个用于更新的 任务

每个任务都会经过 优先级计算(lane模型排序),计算结果决定了任务的紧急程度

用户的 IO行为 优先级最高。正常情况下,当前帧渲染完组件后还会有剩余时间,所以不用担心IO阻塞问题。但如果当前帧的时间不够用时就需要将正在执行的任务暂停,优先处理IO行为,防止 IO阻塞

任务暂停后等到下一帧再进行调度,从上次暂停的地方继续进行。

以上便是React调度的简单概念。

最后,猜猜在这个过程中各位“看官老爷”扮演的是什么角色呢?