React|React Effects List大重构,是为了他()
大家好,我卡颂。
本文我们来看React
内部Effects List
机制重构的前因后果。
阅读完本文,你可以掌握React18
对比之前版本,Suspense
特性的差异及原因。
欢迎加入人类高质量前端框架群,带飞
什么是副作用
简易的React
工作原理可以概括为:
- 触发
更新
- render阶段:计算
更新
会造成的副作用
- commit阶段:执行
副作用
副作用
包含很多类型,比如:Placement
指DOM节点的插入与移动
Passive
指useEffect
回调执行ChildDeletion
指移除子DOM节点
- 等等
DOM
变化主要就是Placement
、ChildDeletion
在起作用。那么
render阶段
如何保存副作用
,commit阶段
又是如何使用副作用
的呢?Effects List 在重构前,
render阶段
,带有副作用
的节点会连接形成链表,这条链表被称为Effects List
。比如下图,B、C、E存在
副作用
,连接形成Effects List
:![React|React Effects List大重构,是为了他()](https://img.it610.com/image/info9/46fde320c323465a87b11982487f41f0.jpg)
文章图片
commit阶段
不需要从A向下遍历整棵树,只需要遍历Effects List
就能找到所有有副作用
的节点并执行对应操作。SubtreeFlags 在重构之后,会将子节点的
副作用
冒泡到父节点的SubtreeFlags
属性。比如B、C、E包含的
副作用
如下图:![React|React Effects List大重构,是为了他()](https://img.it610.com/image/info9/1c0a980fbd664043aec3424fd3d3eb3a.jpg)
文章图片
冒泡流程如下:
- B的
副作用
为Passive
,冒泡到A,A.SubtreeFlags
包含Passive
- E的
副作用
为Placement
,冒泡到D,D.SubtreeFlags
包含Placement
- D冒泡到C,
C.SubtreeFlags
包含Placement
- C的
副作用
为Update
,C.SubtreeFlags
包含Placement
,C冒泡到A - 最终
A.SubtreeFlags
包含Passive
、Placement
、Update
在
commit阶段
,再根据SubtreeFlags
一层层查找有副作用
的节点并执行对应操作。可见,
SubtreeFlags
需要遍历树,而Effects List
只需要遍历链表,效率更高。那么React
为什么要重构呢?Suspense 答案是:
SubtreeFlags
遍历子树的操作虽然比Effects List
需要遍历更多节点,但是React18
中一种新特性恰恰需要遍历子树。这个特性就是
Suspense
。Suspense
是v16
就提供的功能,但v18
之后,当开启并发功能,Suspense
与之前版本的行为是有区别的。考虑如下组件:
loading...
}>
其中
LazyCpn
是使用React.lazy
包裹的异步加载组件
。Sibling
代码如下:function Sibling() {
useEffect(() => {
console.log("Sibling effect");
}, []);
return Sibling;
}
由于
Suspense
会等待子孙组件中的异步请求完毕后再渲染,所以当代码运行时页面首先会渲染fallback
:loading...
但是
Sibling
并不是异步的!这里就体现了新旧版本React
的差异。新旧版React的差异 再回顾下开篇介绍的简易
React
工作原理:- 触发
更新
- render阶段:协调器计算
更新
会造成的副作用
- commit阶段:渲染器执行
副作用
React
保证一次render阶段
对应一次commit阶段
。所以在上例中,虽然由于
LazyCpn
在请求导致Suspense
渲染fallback
,但是并不会阻止Sibling
渲染,也不会阻止Sibling
中useEffect
的执行。控制台还是会打印Sibling effect。
同时,为了在视觉上显得
Sibling
没有渲染,Sibling
渲染的DOM节点
会被设置display: none
:![React|React Effects List大重构,是为了他()](https://img.it610.com/image/info9/018cf19258a644cfaea224f161301a67.jpg)
文章图片
但这其实挺
hack
的。毕竟根据Suspense
的理念,如果子孙组件有异步加载的内容,那应该只渲染fallback
(而不是同时渲染display: none
的内容)所以在新版中,针对
Suspense
内不显示的子树做了单独的处理,既不会渲染display: none
的内容,也不会执行useEffect
回调:![React|React Effects List大重构,是为了他()](https://img.it610.com/image/info9/de2ac1ec946b43beb819c86c2a02c4d6.jpg)
文章图片
要实现这部分处理的基础,就是改变
commit阶段
遍历的方式,也就回到开篇提到的Effects List
重构为subtreeFlags
。
你可以从这个
在线Demo直观的感受新旧版
Suspense
的差异
总结
今天我们又学到了一个React
源码小知识。值得一提的是,针对
Suspense
的这次改进,为React
带来一种新的内部组件类型 —— Offscreen Component
。【React|React Effects List大重构,是为了他()】未来他可能是实现
React
版keep-alive
的基础。推荐阅读
- react|react 安装
- Quartz|Quartz 源码解析(四) —— QuartzScheduler和Listener事件监听
- Flutter的ListView
- 1.2序列通用操作
- React.js学习笔记(17)|React.js学习笔记(17) Mobx
- React.js的表单(六)
- Java应该在哪里判断List是否为空
- 【React|【React Native填坑之旅】从源码角度看JavaModule注册及重载陷阱
- react-navigation|react-navigation 动态修改 tabBar 样式
- Flutter|Flutter SwiftUI React 对比