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

开发 前端
本文我们来看React内部Effects List机制重构的前因后果。你可以掌握React18对比之前版本,Suspense特性的差异及原因。

[[437450]]

大家好,我卡颂。

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

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

什么是副作用

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

1.触发更新

2.render阶段:计算更新会造成的副作用

3.commit阶段:执行副作用

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

  • Placement指DOM节点的插入与移动
  • Passive指useEffect回调执行
  • ChildDeletion指移除子DOM节点
  • 等等

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

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

Effects List

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

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

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

SubtreeFlags

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

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

冒泡流程如下:

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

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

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

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

Suspense

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

这个特性就是Suspense。

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

考虑如下组件:

  1. <Suspense fallback={<h3>loading...</h3>}> 
  2.   <LazyCpn /> 
  3.   <Sibling /> 
  4. </Suspense> 

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

Sibling代码如下:

  1. function Sibling() { 
  2.   useEffect(() => { 
  3.     console.log("Sibling effect"); 
  4.   }, []); 
  5.  
  6.   return <h1>Sibling</h1>; 

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

  1. <h3>loading...</h3> 

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

新旧版React的差异

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

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

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

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

控制台还是会打印「Sibling effect」。

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

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

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

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

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

总结

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

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

未来他可能是实现React版keep-alive的基础。

参考资料

[1]在线Demo:

https://codesandbox.io/s/frosty-currying-35olk?file=/src/App.js

 

责任编辑:姜华 来源: 魔术师卡颂
相关推荐

2012-01-05 09:26:56

App Store作产品赚钱

2016-12-16 12:06:09

数据分析大数据

2011-12-16 16:37:02

Fabrics边界软件数据中心

2023-10-20 10:09:44

人工智能

2013-03-08 09:54:25

2011-05-05 13:06:54

许小年企业转型

2009-11-11 09:39:01

张汝京辞职

2015-08-13 17:45:08

七牛大数据

2021-02-01 14:11:35

数字货币货币ATM

2013-07-09 14:22:56

Windows 8.1

2016-12-26 14:46:09

宽带无线网络Ovum

2017-04-24 16:44:41

宽带千兆速度

2017-09-20 14:10:04

大数据数据分析网红店

2016-08-25 17:46:31

代码组织CRM

2023-04-19 15:30:00

OpenJDKJava

2022-11-03 09:46:08

2020-06-04 07:55:33

ReentrantLo Java

2020-05-06 09:10:46

AQS同步器CAS

2016-10-19 12:54:15

数据聚类关联

2011-09-26 10:13:02

微软数据中心云计算
点赞
收藏

51CTO技术栈公众号