React18已经进入RC(release candidate)阶段,距离正式版只有一步之遥了。

v18新增了很多特性,今天,我们不聊新特性,而是来讲讲v18相比老版更优秀的一个细节:

v18中,组件render的次数可能更少
欢迎加入人类高质量前端框架群,带飞

状态从何而来
在如下组件中:

  1. function App() {
  2. const [num, update] = useState(0);
  3. // ...省略
  4. }

App组件render后会执行useState,返回num的最新值。

也就是说,组件必须render,才能知道最新的状态。为什么会这样呢?

考虑如下触发更新的代码:

  1. const [num, update] = useState(0);
  2. const onClick = () => {
  3. update(100);
  4. update(num => num + 1);
  5. update(num => num * 3);
  6. }

onClick执行后触发更新,更新导致App组件render,进而useState执行。

在useState内部,会遵循如下流程计算num:

update(100)将num变为100
update(num => num + 1)将num变为100 + 1 = 101
update(num => num 3)将num变为101 3 = 303
即,App组件render时,num为303。

所以,状态的计算需要先收集触发的更新,再在useState中统一计算。

对于上述例子,将更新分别命名为u0~u2,则状态的计算公式为:

  1. baseState -> u0 -> u1 -> u2 = newState

Concurrent带来的变化
Concurrent(并发)为React带来了优先级的概念,反映到状态计算上,根据触发更新的场景,更新拥有不同优先级(比如onClick回调中触发的更新优先级高于useEffect回调中触发的更新)。

表现在计算状态中的区别就是,如果某个更新优先级低,则会被跳过。

假设上述例子中u1优先级低,那么App组件render时,计算num状态的公式为:

  1. // 其中u1因为优先级低,被跳过
  2. baseState -> u0 -> u2 = newState

即:

update(100)将num变为100
update(num => num 3)将num变为100 3 = 300
显然这个结果是不对的。

所以,并发情况下React计算状态的逻辑会更复杂。具体来讲,可能包含多轮计算。

当计算状态时,如果某次更新被跳过,则下次计算时会从被跳过的更新继续往后计算。

比如上例中,u1被跳过。当u1被跳过时,num为100。此时的状态100,以及u1和他后面的所有更新都会保存下来,参与下次计算。

在例子中即为u1、u2保存下来。

下次更新的情况如下:

初始状态为100,update(num => num + 1)将num变为100 + 1 = 101
update(num => num 3)将num变为101 3 = 303
可见,最终的结果303与同步的React是一致的,只是需要render两次。

同步的React render一次,结果为303。

并发的React render两次,结果分别为300(中间状态),303(最终状态)。

新旧Concurrent的区别
从上例我们发现,组件render的次数受有多少更新被跳过影响,实际可能不止render两次,而是多次。

在老版并发的React中,表示优先级的是一个被称为expirationTime的时间戳。比较更新是否应该被跳过的算法如下:

  1. // 更新优先级是否小于render的优先级
  2. if (updateExpirationTime < renderExpirationTime) {
  3. // ...被跳过
  4. } else {
  5. // ...不跳过
  6. }

在这种逻辑下,只要优先级低,就会被跳过,就意味着多一次render。

在新版并发的React中,优先级被保存在31位的二进制数中。

举个例子:

  1. const renderLanes = 0b0101;
  2. u1.lane = 0b0001;
  3. u2.lane = 0b0010;

其中renderLanes是本次更新指定的优先级。

比较优先级的函数为:

  1. function isSubsetOfLanes(set, subset) {
  2. return (set & subset) === subset;
  3. }

其中:

  1. // true
  2. isSubsetOfLanes(renderLanes, u1.lane)
  3. // false
  4. isSubsetOfLanes(renderLanes, u2.lane)

u1.lane包含于renderLanes中,代表这个更新拥有足够优先级。

u2.lane不包含于renderLanes中,代表这个更新没有足够优先级,被跳过。

但是被跳过的更新(例子中的u2)的lane会被重置为0,即:

  1. u2.lane = 0b0000;

显然任何lanes都是包含0的:

  1. // true
  2. isSubsetOfLanes(renderLanes, 0)

所以这个更新一定会在下次处理。换言之,在新版并发的React中,由于优先级原因被跳过,导致的重复render,最多只会有2次。

总结
相比于老版并发的React,新版并发的React在render次数上会更有优势。

反映到用户的感官上,用户会更少看到未计算完全的中间状态。

更多相关文章

  1. Android(安卓)增量更新实例
  2. Android实现网络音乐播放器
  3. Ubuntu android sdk manager 无法更新
  4. Android(安卓)获取时间实时更新UI
  5. mybatis如何批量修改数据
  6. android添加通知到顶部任务栏
  7. android环境搭建
  8. android gps经纬度实时更新,获取卫星数量
  9. android 自动更新apk版本

随机推荐

  1. centos7下自动编译升级openssh和OpenSSL
  2. JavaScript 闭包基本指南 [每日前端夜话0
  3. Android(安卓)bionic缺失pthread_cancel
  4. 内外网文件单向传输服务器搭建 samba+rsy
  5. 我的第一篇博客
  6. 学习C的第三天-函数
  7. 只会爬虫不会反爬虫?动图详解利用 User-Ag
  8. 学习C的第三天-if语句
  9. Linux学习之常用的Linux文件内容查看命令
  10. 爬虫又报错了?用 Scrapy 来一发邮件不就好