当前位置: 首页 > Web前端 > JavaScript

ReactEffectsList的大重构是为了他?

时间:2023-03-27 14:24:51 JavaScript

大家好,我是Kason。在本文中,我们将了解重构React内部的EffectsList机制的前因后果。阅读本文后,您可以掌握React18中Suspense特性与之前版本的差异和原因。欢迎加入人类优质前端框架群。副作用是什么?React的简单工作原理可以概括为:triggerupdaterenderstage:计算update引起的sideeffectcommitstage:executesideeffectInsertionandmovementPassive是指useEffect回调执行ChildDeletion是指移除childDOM节点等。更新引起DOM变化主要是因为Placement和ChildDeletion在起作用。那么render阶段如何保存sideeffects,commit阶段如何使用sideeffects呢?在重构EffectsList之前,在渲染阶段,会将有副作用的节点连接起来,形成一个链表,称为EffectsList。比如下图中,B、C、E有副作用,它们连接起来形成一个EffectsList:commit阶段不需要从A向下遍历整棵树,只需要遍历Effects列表找出所有有副作用的节点,并进行相应的操作。SubtreeFlags重构后,子节点的副作用会冒泡到父节点的SubtreeFlags属性中。例如B、C、E包含的sideeffects如下:冒泡过程如下:B的sideeffect是Passive,它冒泡到A,A.SubtreeFlags包含PassiveE的sideeffect,它冒泡到D,并且D.SubtreeFlags包含PlacementD。到C,C.SubtreeFlags包含Placement,C的副作用是Update,C.SubtreeFlags包含Placement,C冒泡到A最后A.SubtreeFlags包含Passive,Placement,Update,也就是说A的子树包含这三个side效果。在commit阶段,根据SubtreeFlags,逐层寻找有副作用的节点,并进行相应的操作。可以看出,SubtreeFlags需要遍历树,而EffectsList只需要遍历链表,效率更高。那么为什么要重构React呢?Suspense的回答是:虽然SubtreeFlags需要遍历比EffectsList更多的节点来遍历子树,但是React18中的一个新特性只是需要遍历子树。此功能是悬念。Suspense是v16中提供的功能,但是在v18之后,开启并发功能后,Suspense的行为与之前的版本有所不同。考虑以下组件:loading...}>其中LazyCpn是用React.lazy包装的异步加载组件。兄弟代码如下:functionSibling(){useEffect(()=>{console.log("Siblingeffect");},[]);return

Sibling

;}因为Suspense会等待后代组件完成后渲染异步请求,所以当代码运行时,页面会先渲染fallback:

loading...

但是Sibling不是异步的!下面是React新旧版本的区别。React新老版本的区别我们来回顾一下最开始介绍的简单React的工作原理:触发更新渲染阶段:协调器计算更新提交阶段带来的副作用:渲染器在启用前执行副作用并发性,React保证一个渲染阶段对应一个提交阶段。所以在上面的例子中,虽然LazyCpn请求的是Suspense渲染回退,但是并不会阻止Sibling的渲染,也不会阻止Sibling中useEffect的执行。控制台仍会打印Sibling效果。同时,为了直观的显示Sibling没有被渲染,Sibling渲染的DOM节点会被设置为display:none:但这其实是相当hack的。毕竟按照Suspense的理念,如果子孙组件有异步加载的内容,应该只渲染fallback(而不是同时渲染display:none的内容)。因此,在新版本中,对于Suspense中没有显示的子树进行了单独的处理。既不会渲染display:none的内容,也不会执行useEffect回调:这部分处理的基础是改变commit阶段的遍历方式,返回开头提到的EffectsList,重构为subtreeFlags.通过这个在线演示,你可以直观的感受到新旧版本Suspense的区别总结今天我们又学习了一个关于React源码的小知识。值得一提的是,本次对Suspense的改进为React带来了一种新型的内部组件——OffscreenComponent。以后他可能是实现react版keep-alive的基础。