Open jtwang7 opened 3 years ago
useEffect 在渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。 useLayoutEffect 在渲染时是同步执行,其执行时机与 componentDidMount,componentDidUpdate 一致
useLayoutEffect,因为从源码中调用的位置来看,useLayoutEffect的 create 函数的调用位置、时机都和 componentDidMount,componentDidUpdate 一致,且都是被 React 同步调用,都会阻塞浏览器渲染。
同上,useLayoutEffect 的 detroy 函数的调用位置、时机与 componentWillUnmount 一致,且都是同步调用。useEffect 的 detroy 函数从调用时机上来看,更像是 componentDidUnmount (注意React 中并没有这个生命周期函数)。
可以看到在流程9/10期间,DOM 已经被修改,但但浏览器渲染线程依旧处于被阻塞阶段,所以还没有发生回流、重绘过程。由于内存中的 DOM 已经被修改,通过 useLayoutEffect 可以拿到最新的 DOM 节点,并且在此时对 DOM 进行样式上的修改,假设修改了元素的 height,这些修改会在步骤 11 和 react 做出的更改一起被一次性渲染到屏幕上,依旧只有一次回流、重绘的代价。 如果放在 useEffect 里,useEffect 的函数会在组件渲染到屏幕之后执行,此时对 DOM 进行修改,会触发浏览器再次进行回流、重绘,增加了性能上的损耗。
React - useLayoutEffect 和 useEffect 的执行时机
参考文章: 深入理解 React useLayoutEffect 和 useEffect 的执行时机
React 官方解释
useEffect useEffect 的执行时机:赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。
useLayoutEffect useLayoutEffect 的执行时机:赋值给 useLayoutEffect 的函数会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
从 React 执行流程解释
当 react 组件状态发生改变时,会触发 react diff,render() 会创建出一个新的 React Element,diffing 算法会比较新旧 React Element,并在 diff 结束后进入到 commit 阶段,准备把虚拟 DOM 发生的变化映射到真实 DOM 上。
在 commit 阶段的前期,会调用一些生命周期方法,对于类组件来说,需要触发组件的 getSnapshotBeforeUpdate 生命周期,对于函数组件,此时会调度 useEffect 的 create/destroy 函数。
随后,就到了 React 把虚拟 DOM 设置到真实 DOM 上的阶段,这个阶段主要调用的函数是 commitWork,commitWork 函数会针对不同的 fiber 节点调用不同的 DOM 的修改方法 (比如文本节点和元素节点的修改方法是不一样的) 对 DOM 节点进行更新。