共计 1980 个字符,预计需要花费 5 分钟才能阅读完成。
大家好,我卡颂。
本文咱们来看 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
:
commit 阶段
不须要从 A 向下遍历整棵树,只须要遍历 Effects List
就能找到所有有 副作用
的节点并执行对应操作。
SubtreeFlags
在重构之后,会将子节点的 副作用
冒泡到父节点的 SubtreeFlags
属性。
比方 B、C、E 蕴含的 副作用
如下图:
冒泡流程如下:
- 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
这就代表 A 的子树中蕴含这三种副作用。
在 commit 阶段
,再依据SubtreeFlags
一层层查找有 副作用
的节点并执行对应操作。
可见,SubtreeFlags
须要遍历树,而 Effects List
只须要遍历链表,效率更高。那么 React
为什么要重构呢?
Suspense
答案是:SubtreeFlags
遍历子树的操作尽管比 Effects List
须要遍历更多节点,然而 React18
中一种新个性恰好须要 遍历子树。
这个个性就是Suspense
。
Suspense
是 v16
就提供的性能,但 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
工作原理:
- 触发
更新
- 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 直观的感触新旧版
Suspense
的差别
总结
明天咱们又学到了一个 React
源码小常识。
值得一提的是,针对 Suspense
的这次改良,为 React
带来一种新的外部组件类型 —— Offscreen Component
。
将来他可能是实现 React
版keep-alive
的根底。