为什么 useEffect 中无法获取最新真实 dom,useLayoutEffect 可以

287 阅读3分钟

首先我们要了解 useLayoutEffect 这个同步执行函数

官网解释:useLayoutEffect 是 useEffect 的一个版本,在浏览器重新绘制屏幕之前触发。

useLayoutEffect 是一个同步函数,内部的代码和所有计划的状态更新阻塞了浏览器重新绘制屏幕。如果过度使用会使你的应用程序变慢。尽量不使用

通常开发过程中都是使用 useEffect 处理副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行,useLayoutEffect 会在 render,DOM 更新之后同步触发函数,优先于 useEffect

简单理解就是执行的时机不同,useLayoutEffect 和 componentDidMount&componentDidUpdate 一致,在 react 完成 DOM 更新后马上同步调用代码,会阻塞页面渲染,而 useEffect 会在整个页面渲染完才会调用代码

所以在使用 useEffect 的时候,可能会出现页面抖动的现象

export default function Test() {
  const [count, setCount] = useState(10);
  // 这里使用 useEffect 页面会有抖动的现象,使用 useLayoutEffect 就不会存在抖动的现象
  useEffect(() => {
    if (count === 6666666666) {
      setCount(200 + Math.random());
    }
  }, [count]);
  return (
    <div>
      <h2>数字: {count}</h2>
      <button onClick={(e) => setCount(6666666666)}>修改数字</button>
    </div>
  );
}

我们要了解两个钩子的触发时机,useLayoutEffect 会在 useEffect 之前执行,那为什么 useLayoutEffect 能够获取到最新 dom 元素而 useEffect 却不能呢?

这个时候就需要解释一下浏览器对于处理获取元素的方法处理方式了,实际上浏览器对于处理获取元素的方法处理方式有两种:

1.从刚刚生成还未来得及绘制的真实 DOM 树上获取(useLayoutEffect):当我们浏览器中存在最新的等待绘制的真实 dom 时,我们调用获取元素方法时,浏览器会将等待绘制的真实dom上的元素返回。

2.从已经绘制完成或正在绘制的页面上获取(useEffect):当我们浏览器中不存在最新的等待绘制的真实 dom 时,这时候获取元素,浏览器会将已经完成绘制或正在绘制的页面上的元素返回

两种获取 dom 的处理方式,真实 dom 优先级大于已绘制的 dom,因此当 useLayoutEffect 中获取 dom 元素时,浏览器会将 js 引擎中生成的等待绘制的真实 dom 元素返回,此时的 dom 元素就是最新的元素。而在 useEffect 中获取的时候已经到了重新绘制的阶段,浏览器只能从正在绘制的页面上获取元素返回,虽然理论上将 useEffect 函数的执行和页面的绘制是同步进行的,但是实际上,JS 执行速度和页面绘制速度之间的差距可以是相当大的,所以即便两者是同时开始的,但是计算机内部执行速度来说,基本上绘制刚刚开始,useEffect 函数都已经执行完了,所以在 useEffect 回调函数中获取元素,几乎不可能获取到最新的元素。