乐趣区

关于javascript:React-Effects-List大重构是为了他

大家好,我卡颂。

本文咱们来看 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

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 的 副作用 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

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

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

要实现这部分解决的根底,就是扭转 commit 阶段 遍历的形式,也就回到开篇提到的 Effects List 重构为subtreeFlags

你能够从这个在线 Demo 直观的感触新旧版 Suspense 的差别

总结

明天咱们又学到了一个 React 源码小常识。

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

将来他可能是实现 Reactkeep-alive的根底。

退出移动版