咱们先看下 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?
流程
- react 在 diff 后,会进入到 commit 阶段,筹备把虚构 DOM 产生的变动映射到实在 DOM 上
- 在 commit 阶段的后期,会调用一些生命周期办法,对于类组件来说,须要触发组件的 getSnapshotBeforeUpdate 生命周期,对于函数组件,此时会调度 useEffect 的 create destroy 函数
- 留神是调度,不是执行。在这个阶段,会把应用了 useEffect 组件产生的生命周期函数入列到 React 本人保护的调度队列中,给予一个一般的优先级,让这些生命周期函数异步执行
// 能够近似的认为,React 做了这样一步,理论流程中要简单的多
setTimeout(() => {
const preDestory = element.destroy;
if (!preDestory) prevDestroy();
const destroy = create();
element.destroy= destroy;
}, 0);
- 随后,就到了 React 把虚构 DOM 设置到实在 DOM 上的阶段,这个阶段次要调用的函数是 commitWork,commitWork 函数会针对不同的 fiber 节点调用不同的 DOM 的批改办法,比方文本节点和元素节点的批改办法是不一样的。
- commitWork 如果遇到了类组件的 fiber 节点,不会做任何操作,会间接 return,进行收尾工作,而后去解决下一个节点,这点很容易了解,类组件的 fiber 节点没有对应的实在 DOM 构造,所以就没有相干操作
- 但在有了 hooks 当前,函数组件在这个阶段,会 同步调用 上一次渲染时 useLayoutEffect(create, deps) create 函数返回的 destroy 函数
- 留神一个节点在 commitWokr 后,这个时候,咱们曾经把产生的变动映射到实在 DOM 上了
- 但因为 JS 线程和浏览器渲染线程是互斥的,因为 JS 虚拟机还在运行,即便内存中的实在 DOM 曾经变动,浏览器也没有立即渲染到屏幕上
- 此时会进行收尾工作,同步执行 对应的生命周期办法,咱们说的 componentDidMount,componentDidUpdate 以及 useLayoutEffect(create, deps) 的 create 函数都是在这个阶段被 同步执行。
- 对于 react 来说,commit 阶段是不可打断的,会一次性把所有须要 commit 的节点全副 commit 完,至此 react 更新结束,JS 进行执行
- 浏览器把发生变化的 DOM 渲染到屏幕上,到此为止 react 仅用一次回流、重绘的代价,就把所有须要更新的 DOM 节点全副更新实现
- 浏览器渲染实现后,浏览器告诉 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 进行批改,会触发浏览器再次进行回流、重绘,减少了性能上的损耗。