React Effects List大重构,是为了他?

熵桥薄雾
• 阅读 3521

大家好,我卡颂。

本文我们来看React内部Effects List机制重构的前因后果。

阅读完本文,你可以掌握React18对比之前版本,Suspense特性的差异及原因。

欢迎加入人类高质量前端框架群,带飞

什么是副作用

简易的React工作原理可以概括为:

  1. 触发更新
  2. render阶段:计算更新会造成的副作用
  3. commit阶段:执行副作用

副作用包含很多类型,比如:

  • PlacementDOM节点的插入与移动
  • PassiveuseEffect回调执行
  • ChildDeletion指移除子DOM节点
  • 等等

更新造成DOM变化主要就是PlacementChildDeletion在起作用。

那么render阶段如何保存副作用commit阶段又是如何使用副作用的呢?

Effects List

在重构前,render阶段,带有副作用的节点会连接形成链表,这条链表被称为Effects List

比如下图,B、C、E存在副作用,连接形成Effects List

React Effects List大重构,是为了他?

commit阶段不需要从A向下遍历整棵树,只需要遍历Effects List就能找到所有有副作用的节点并执行对应操作。

SubtreeFlags

在重构之后,会将子节点的副作用冒泡到父节点的SubtreeFlags属性。

比如B、C、E包含的副作用如下图:

React Effects List大重构,是为了他?

冒泡流程如下:

  1. B的副作用Passive,冒泡到A,A.SubtreeFlags包含Passive
  2. E的副作用Placement,冒泡到D,D.SubtreeFlags包含Placement
  3. D冒泡到C,C.SubtreeFlags包含Placement
  4. C的副作用UpdateC.SubtreeFlags包含Placement,C冒泡到A
  5. 最终A.SubtreeFlags包含PassivePlacementUpdate

这就代表A的子树中包含这三种副作用。

commit阶段,再根据SubtreeFlags一层层查找有副作用的节点并执行对应操作。

可见,SubtreeFlags需要遍历树,而Effects List只需要遍历链表,效率更高。那么React为什么要重构呢?

Suspense

答案是:SubtreeFlags遍历子树的操作虽然比Effects List需要遍历更多节点,但是React18中一种新特性恰恰需要遍历子树

这个特性就是Suspense

Suspensev16就提供的功能,但v18之后,当开启并发功能,Suspense与之前版本的行为是有区别的。

考虑如下组件:

<Suspense fallback={<h3>loading...</h3>}>
  <LazyCpn />
  <Sibling />
</Suspense>

其中LazyCpn是使用React.lazy包裹的异步加载组件

Sibling代码如下:

function Sibling() {
  useEffect(() => {
    console.log("Sibling effect");
  }, []);

  return <h1>Sibling</h1>;
}

由于Suspense会等待子孙组件中的异步请求完毕后再渲染,所以当代码运行时页面首先会渲染fallback

<h3>loading...</h3>

但是Sibling并不是异步的!这里就体现了新旧版本React的差异。

新旧版React的差异

再回顾下开篇介绍的简易React工作原理:

  1. 触发更新
  2. render阶段:协调器计算更新会造成的副作用
  3. commit阶段:渲染器执行副作用

在开启并发之前,React保证一次render阶段对应一次commit阶段

所以在上例中,虽然由于LazyCpn在请求导致Suspense渲染fallback,但是并不会阻止Sibling渲染,也不会阻止SiblinguseEffect的执行。

控制台还是会打印Sibling effect

同时,为了在视觉上显得Sibling没有渲染,Sibling渲染的DOM节点会被设置display: none

React Effects List大重构,是为了他?

但这其实挺hack的。毕竟根据Suspense的理念,如果子孙组件有异步加载的内容,那应该只渲染fallback(而不是同时渲染display: none的内容)

所以在新版中,针对Suspense不显示的子树做了单独的处理,既不会渲染display: none的内容,也不会执行useEffect回调:

React Effects List大重构,是为了他?

要实现这部分处理的基础,就是改变commit阶段遍历的方式,也就回到开篇提到的Effects List重构为subtreeFlags

你可以从这个在线Demo直观的感受新旧版Suspense的差异

总结

今天我们又学到了一个React源码小知识。

值得一提的是,针对Suspense的这次改进,为React带来一种新的内部组件类型 —— Offscreen Component

未来他可能是实现Reactkeep-alive的基础。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
3年前
List的Select 和Select().tolist()
List<PersondelpnewList<Person{newPerson{Id1,Name"小明1",Age11,Sign0},newPerson{Id2,Name"小明2",Age12,
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
Linux自动检测网站心跳通知shell脚本
!/bin/bashLIST("http://xxxx.com")NAME("评价系统getwindowList接口")for((i0;i<${LIST@};i))doHTTP_CODEcurlo/dev/nullsw"%{http_code}""${LIST
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(