likes
comments
collection
share

React源码解析系列(十)------ lane优先级

作者站长头像
站长
· 阅读数 46

前言

本文是这个系列最后一章,而本文的内容也是理解React渲染机制的关键,lane优先级和fiber树的配合可以说是非常完美的,因为lane优先级的存在,优化了同步更新,减少了页面阻塞的情况,优化了触发事件的反馈。话不多说,我们直接开始今天的主要内容。

lane优先级

31个赛道与32个优先级

lane优先级共有31个赛道但有32个优先级,看到32这个数字,相信很多读者觉得非常熟悉,没错,那就是二进制,在React中,一个bit就代表一个赛道,共32个bit,由于最左边的bit需要用来表示正负号(取反),所以就只剩下31个bit,这就是为什么只有31个赛道。至于32个优先级怎么来的,那是因为当所有bit上都为0时,表示无优先级,这个无优先级也算一个优先级。

除此之外,还记得上一篇的schedule优先级吗?现在一共有两种优先级了,不会混乱吗?

答案是当然不会混乱,而且实际上不止这两种优先级,还有第三种优先级,那就是事件优先级。尽管到目前为止有这三种优先级,但其实都是可以转换成一种的,这里直接看下图,相信大家都能明白了。

React源码解析系列(十)------ lane优先级

上图中lane优先级一共有31个块,每一个块就代表一个bit上的1。

打上优先级标识

打优先级标识这个操作是存在于非常多地方的,这里就挑几个大家常见的地方来说说。

  • useEffect
// NormalSchedulerPriority就是默认优先级,为第二高的优先级也就是上图黄色部分
Scheduler_scheduleCallback(NormalSchedulerPriority, effect);
  • 各种事件
function getEventPriority(domEventName) {
    switch (domEventName) {
        // 这个是最高优先级也就是同步优先级
        case '与click同档的事件名称':
            return DiscreteEventPriority;
        // 这个是第二高优先级也就是和effect一样的默认优先级
        case '与drag同档的事件名称':
            return ContinuousEventPriority;
        // 上图橙色部分
        default:
            return DefaultEventPriority;
    }
}

至于有哪些事件是第一档,哪些事件是第二档,这里建议是看源码,毕竟事件真的很多,不好罗列。点这里

批量更新

批量更新是很常见的一个点,而在React18中,批量更新的核心就是lane优先级,React在执行调度前,会跳过所有调度优先级(lane有限,注意是lane优先级而非其他优先级)一致的调度。我们来看个例子。

React源码解析系列(十)------ lane优先级

注意,图中跳过的是setState(2)和setState(3)引起的调度,本身setState这个方法是同步的,他会执行的,结合之前将reducer的章节,setState会将memoizedstate改掉,而调度就是取的这个值,所以在setState(1)引起的调度执行时就会得到最新的状态为3,直接渲染的3。

同步与异步

我们再回到上面的各个优先级的图中,有一个赛道为SyncLane,翻译过来就是同步赛道,其实这个赛道是最特殊的,因为别的优先级都是包含好几个赛道,只有同步优先级仅包括了一个SyncLane。它的特殊也决定了他的强大,名为同步优先级,表现就是最像同步的一个。

在schedule中说道,React在构建fiber树的时候,会在每一帧中向浏览器请求5ms进行fiber树的构建,如果到时间了,就停止,再下一个5ms中继续。而这种看起来很异步的fiber树构建方式其实是只针对非SyncLane的,SyncLane的fiebr树构建任务是不会被打断的,会一直构建下去就算超时了也会继续,如果任务量过于庞大,会导致页面出现卡顿(多帧的时间都被用于js计算而不执行渲染)。这就是这个赛道叫同步的原因。

所以, 我们常谈setState的同步异步问题,其实到这里就有了答案,那就是要看setState被调用时的环境,如果我们是在非SyncLane的环境(如useEffect、onDrag中)下调用的,那么就是异步,如果是SyncLane环(如onClick中)境下被调用的,那么就是同步。

饥饿赛道

优先级的存在仅仅是为了给调度任务排个执行顺序吗?

如果仅仅只是排个顺序,那么我们在一个异步任务的执行过程中插入一个同步任务的话,浏览器就会卡更久了(起码多卡5ms),这样的体验肯定不是那么好。

因此就有了高优先级打断低优先级任务的机制。只要在低优先级任务执行中,如果来了一个高优先级任务,那么就会先去执行高优先级任务。

但是这个机制其实会带来另外一个问题,那就是在低优先级任务执行时,不断的来高优先级任务,那这个低优先级任务岂不是一直得不到执行?

聊到这里,我们先停顿去思考一下React是如何去解决这个问题的。下面放张风景图挡一下答案,想直接看答案的往下看就好啦~

React源码解析系列(十)------ lane优先级

其实我们整篇文章中都没有提及在schedule文章中的一个概念------过期时间!

不同优先级的任务在入队时都会有一个过期时间挂在头上,这个过期时间一旦到了,那这个任务的优先级就会被提到和SyncLane一样。React把这些包含过了过期时间的任务的赛道称为饥饿赛道。在我们每次往队列中添加调度任务的时候都会去检查一下是否有任务的过期时间已经超了,有的话就会把那个赛道标为饥饿赛道,这上面的任务就拥有着SyncLane任务的权利(你被强化了,上去送吧),同步执行!

完整流程

到这里,lane优先级相关的一些特性和机制就聊的差不多了,下面就放上一张大致的流程图。

React源码解析系列(十)------ lane优先级

结尾

本文主要讲了lane优先级的概念,并通过lane优先级配合schedule优先级讲了批量更新,调度的同步异步以及接赛道的特性/现象。

到这里,正如文章开头的前言所说,本文就是React源码系列的最后一篇了,已经完结撒花了,回顾整系列,虽然各篇文章的成果都不太好,但于我个人而言收获是非常大的,在当初接触源码调试源码,感觉自己好像很多东西都弄清楚了,但是真正到输出为掘金文章时,才发现自己对这个体系构建上的漏洞。

啰嗦了几个月,到此再对各位读者啰嗦一次,要想理解整个React运行机制或者各个hook的原理,只看文章其实真的是不够的,还是上手调试比较好,在下的每一篇文章末尾都附带了仓库供各位读者调试,加油吧,jym,一起共勉。

最后的最后,如果各位读者有什么感兴趣的hook的原理,也可以私信我,可以为这个系列增加点番外篇😁

到lane的代码仓库:点这里

完整源码:点这里

上一篇:schedule优先级

转载自:https://juejin.cn/post/7255795059658997818
评论
请登录