React中useEffect与useLayoutEffect的区别

useLayoutEffect的官方定义

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。 可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

尽可能使用标准的 useEffect 以避免阻塞视觉更新。

提示

如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffectcomponentDidMount、componentDidUpdate 的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect

如果你使用服务端渲染,请记住,无论 useLayoutEffect 还是 useEffect 都无法在 Javascript 代码加载完成之前执行。这就是为什么在服务端渲染组件中引入 useLayoutEffect 代码时会触发 React 告警。 解决这个问题,需要将代码逻辑移至 useEffect 中(如果首次渲染不需要这段逻辑的情况下),或是将该组件延迟到客户端渲染完成后再显示(如果直到 useLayoutEffect 执行之前 HTML 都显示错乱的情况下)。

若要从服务端渲染的 HTML 中排除依赖布局 effect 的组件,可以通过使用 showChild && <Child /> 进行条件渲染,并使用 useEffect(() => { setShowChild(true); }, []) 延迟展示组件。这样,在客户端渲染完成之前,UI 就不会像之前那样显示错乱了。

例子

下面使用例子简单的演示这两者的区别。

假设有个场景,一个div,当屏幕分辨率大于600px时宽度100%,当小于600px时宽度设置为200px。

  • 正常使用useEffect实现:
import React, { CSSProperties } from 'react';

const blockStyles: CSSProperties = {
  background: 'pink',
  height: 200,
  width: '100%',
  transition: 'all 1s ease-in'
};

function App() {

  const [mobileStyles, setMobileStyles] = React.useState<CSSProperties>({});

  React.useEffect(() => {
    const mobile = matchMedia('(max-width: 600px)');

    if (mobile.matches) {
      setMobileStyles({ width: 200 });
    }
  }, []);


  return (
    <div>
      <div style={{ ...blockStyles, ...mobileStyles }} />
    </div>
  );
}

export default App;

当你这样做后你会发现,当你的屏幕宽度小于600px时,粉色的div会由100%宽慢慢的变成200px。因为transition: 'all 1s ease-in', 每次刷新,都会这样。

  • useEffect替换成useLayoutEffect,其它保持不变。
React.useLayoutEffect(() => {
  const mobile = matchMedia('(max-width: 600px)');

  if (mobile.matches) {
    setMobileStyles({ width: 200 });
  }
}, []);

使用useLayoutEffect后,刷新则完全不会出现这一动画变化过程,而是直接显示200px的粉色div!