乐趣区

关于前端:React的useLayoutEffect和useEffect执行时机有什么不同

咱们先看下 React 官网文档对这两个 hook 的介绍,建设个整体意识

useEffect(create, deps):

该 Hook 接管一个蕴含命令式、且可能有副作用代码的函数。在函数组件主体内(这里指在 React 渲染阶段)扭转 DOM、增加订阅、设置定时器、记录日志以及执行其余蕴含副作用的操作都是不被容许的,因为这可能会产生莫名其妙的 bug 并毁坏 UI 的一致性。应用 useEffect 实现副作用操作。赋值给 useEffect 的函数会 在组件渲染到屏幕之后执行。你能够把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

useLayoutEffect(create, deps):

其函数签名与 useEffect 雷同,但它 会在所有的 DOM 变更之后同步调用 effect。能够应用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 外部的更新打算将被同步刷新。

留神加粗的字段,React 官网的文档其实把两个 hook 的执行机会说的很分明,上面咱们深刻到 react 的执行流程中来了解下

问题

  • useEffect 和 useLayoutEffect 的区别?
  • useEffect 和 useLayoutEffect 哪一个与 componentDidMount,componentDidUpdate 的是等价的?
  • useEffect 和 useLayoutEffect 哪一个与 componentWillUnmount 的是等价的?
  • 为什么倡议将批改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?

流程

  1. react 在 diff 后,会进入到 commit 阶段,筹备把虚构 DOM 产生的变动映射到实在 DOM 上
  2. 在 commit 阶段的后期,会调用一些生命周期办法,对于类组件来说,须要触发组件的 getSnapshotBeforeUpdate 生命周期,对于函数组件,此时会调度 useEffect 的 create destroy 函数
  3. 留神是调度,不是执行。在这个阶段,会把应用了 useEffect 组件产生的生命周期函数入列到 React 本人保护的调度队列中,给予一个一般的优先级,让这些生命周期函数异步执行
// 能够近似的认为,React 做了这样一步,理论流程中要简单的多

setTimeout(() => {
      const preDestory = element.destroy;
      if (!preDestory) prevDestroy();
      const destroy = create();
      element.destroy= destroy;
}, 0);
  1. 随后,就到了 React 把虚构 DOM 设置到实在 DOM 上的阶段,这个阶段次要调用的函数是 commitWork,commitWork 函数会针对不同的 fiber 节点调用不同的 DOM 的批改办法,比方文本节点和元素节点的批改办法是不一样的。
  2. commitWork 如果遇到了类组件的 fiber 节点,不会做任何操作,会间接 return,进行收尾工作,而后去解决下一个节点,这点很容易了解,类组件的 fiber 节点没有对应的实在 DOM 构造,所以就没有相干操作
  3. 但在有了 hooks 当前,函数组件在这个阶段,会 同步调用 上一次渲染时 useLayoutEffect(create, deps) create 函数返回的 destroy 函数
  4. 留神一个节点在 commitWokr 后,这个时候,咱们曾经把产生的变动映射到实在 DOM 上了
  5. 但因为 JS 线程和浏览器渲染线程是互斥的,因为 JS 虚拟机还在运行,即便内存中的实在 DOM 曾经变动,浏览器也没有立即渲染到屏幕上
  6. 此时会进行收尾工作,同步执行 对应的生命周期办法,咱们说的 componentDidMount,componentDidUpdate 以及 useLayoutEffect(create, deps) 的 create 函数都是在这个阶段被 同步执行
  7. 对于 react 来说,commit 阶段是不可打断的,会一次性把所有须要 commit 的节点全副 commit 完,至此 react 更新结束,JS 进行执行
  8. 浏览器把发生变化的 DOM 渲染到屏幕上,到此为止 react 仅用一次回流、重绘的代价,就把所有须要更新的 DOM 节点全副更新实现
  9. 浏览器渲染实现后,浏览器告诉 react 本人处于闲暇阶段,react 开始执行本人调度队列中的工作,此时才开始执行 useEffect(create, deps) 的产生的函数

解答

useEffect 和 useLayoutEffect 的区别?

useEffect 在渲染时是异步执行,并且要等到浏览器将所有变动渲染到屏幕后才会被执行。

useLayoutEffect 在渲染时是同步执行,其执行机会与 componentDidMount,componentDidUpdate 统一

对于 useEffect 和 useLayoutEffect 哪一个与 componentDidMount,componentDidUpdate 的是等价的?

useLayoutEffect,因为从源码中调用的地位来看,useLayoutEffect 的 create 函数的调用地位、机会都和 componentDidMount,componentDidUpdate 统一,且都是被 React 同步调用,都会阻塞浏览器渲染。参考 前端进阶面试题具体解答

useEffect 和 useLayoutEffect 哪一个与 componentWillUnmount 的是等价的?

同上,useLayoutEffect 的 detroy 函数的调用地位、机会与 componentWillUnmount 统一,且都是同步调用。useEffect 的 detroy 函数从调用机会上来看,更像是 componentDidUnmount (留神 React 中并没有这个生命周期函数)。

为什么倡议将批改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?

能够看到在流程 9 /10 期间,DOM 曾经被批改,但但浏览器渲染线程仍旧处于被阻塞阶段,所以还没有产生回流、重绘过程。因为内存中的 DOM 曾经被批改,通过 useLayoutEffect 能够拿到最新的 DOM 节点,并且在此时对 DOM 进行款式上的批改,假如批改了元素的 height,这些批改会在步骤 11 和 react 做出的更改一起被一次性渲染到屏幕上,仍旧只有一次回流、重绘的代价。

如果放在 useEffect 里,useEffect 的函数会 在组件渲染到屏幕之后执行,此时对 DOM 进行批改,会触发浏览器再次进行回流、重绘,减少了性能上的损耗。

退出移动版