用useEffect还是用useLayoutEffect

用useEffect还是用useLayoutEffect

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

useEffect

默认情况下,useEffect将在每轮渲染结束后异步执行,不同于class Component中的componentDidUpdatecomponentDidMount在渲染后同步执行,useEffect不会阻塞浏览器的渲染。

useLayoutEffect

useLayoutEffect的作用几乎与useEffect一致,不同的是,useLayoutEffect是同步执行的,与componentDidUpdatecomponentDidMount执行机制一样,在DOM更新后,在浏览器渲染这些更改之前,立即执行。

使用场景

项目中几乎99%的情况下,使用useEffect.

场景一

  • 如果组件在状态更新时会闪一下:即开始显示初始状态的值,然后显示更新后的值,中间变换时间间隔非常短,看起来的交互就会像是闪一下,如果要避免这情况的话,可以把useEffect替换为useLayoutEffect
  • 这正是因为,useLayoutEffect是在本次更新准备完成之前,同步执行它的回调函数(它会阻塞页面渲染),而 useLayoutEffect 内部还有触发组件更新的操作,所以初始状态的虚拟DOM的值会被二次重新更新,完成之后才会更新至真实DOM,最终表现出只更新一个 DOM的效果,减少了真实 DOM 的回流或重绘的消耗,所以不会有闪烁的情况。如下代码:
    import React, {
      useState,
      useLayoutEffect
    } from 'react';
    import ReactDOM from 'react-dom';

    const BlinkyRender = () => {
      const [value, setValue] = useState(0);

      useLayoutEffect(() => {
        if (value === 0) {
          setValue(10 + Math.random() * 200);
        }
      }, [value]);

      return (
        <div onClick={() => setValue(0)}>
          value: {value}
        </div>
      );
    };

    ReactDOM.render(
      <BlinkyRender />,
      document.querySelector('#root')
    );

复制代码

场景二

在任何其它可能会更新一个变量(比如ref)的代码运行前,如果你想访问这个变量此时最新的值,可以用useLayoutEffect,以下面代码为例:

    const ref = React.useRef()
    React.useEffect(() => {
      ref.current = 'new value' // 影响 ref更新的代码
    })

    React.useLayoutEffect(() => {
      console.log(ref.current) // 此时获取的为更新前的值,而不是 "new value"
    })
复制代码

总结

  • 大部分99%情况下使用异步执行非阻塞的useEffect,用户更快的可以看到DOM更新
  • 非特殊情况下一般不建议使用useLayoutEffect
  • useLayoutEffect的执行时机是在DOM更新后,浏览器完成渲染(绘制)之前执行

参考

useEffect vs useLayoutEffect
When to useLayoutEffect Instead of useEffect

分类:
前端
标签: