前端 - React - - useEffect和useLayoutEffect的区别

useEffect和useLayoutEfeect都是处理副作用函数,他俩的区别是在执行时间与事件机制上。

1. 核心区别:

  • useEffect(异步执行):在浏览器绘制完成之后异步执行。它不会阻塞浏览器的视觉更新,适合用于获取数据、事件绑定、日志记录等不直接影响当前帧 UI 的操作。

  • useLayoutEffect(同步执行):在浏览器绘制之前同步执行。它会阻塞浏览器的渲染,直到回调执行完毕。适合用于需要直接操作 DOM 样式、测量 DOM 尺寸,且需要避免 UI 闪烁的场景。

2. 分步分析执行时机

为了直观理解,我们看一下 React 组件更新时的完整时间线:

  1. 触发更新:State 或 Props 改变。

  2. Render 阶段:React 执行组件函数,计算 Virtual DOM。

  3. Commit 阶段 :React 将 Virtual DOM 变更映射到真实的 DOM 节点(此时 DOM 已更新,但屏幕尚未绘制)。

  4. useLayoutEffect 执行 (同步阻塞):React 在此处立即 执行所有 useLayoutEffect 回调。

  5. 浏览器绘制(Paint):浏览器终于将 DOM 节点绘制到屏幕上(变成像素)。

  6. useEffect 执行 (异步):React 在浏览器绘制完成后,利用 requestIdleCallback 或微任务机制,延迟 执行 useEffect 回调。

核心结论useLayoutEffect 执行时,你可以拿到最新的 DOM 节点,但屏幕还是旧的(还没画);useEffect 执行时,屏幕已经变成新的了。

常见问题:使用useEffect时,会出现页面闪烁问题:

假设你有一个组件,渲染时依赖某个 DOM 节点的宽度,且该宽度需要动态计算后设置高度。

如果用 useEffect(错误示例):

javascript 复制代码
function BrokenComponent() {
  const ref = useRef();
  useEffect(() => {
    const height = ref.current.getBoundingClientRect().width;
    ref.current.style.height = `${height}px`; // 修改 DOM
  }, []);
  return <div ref={ref}>内容</div>;
}

页面会先渲染旧的高度(比如高度为0),然后瞬间"跳"到计算后的新高度。用户会看到明显的抖动或闪烁

如果用 useLayoutEffect(正确示例):

javascript 复制代码
function FixedComponent() {
  const ref = useRef();
  useLayoutEffect(() => {
    const height = ref.current.getBoundingClientRect().width;
    ref.current.style.height = `${height}px`;
  }, []);
  return <div ref={ref}>内容</div>;
}

React 在绘制前就修改了高度,浏览器绘制时直接使用了最终尺寸,用户看到的是稳定且平滑的界面。

注意:在 SSR 环境下(服务端渲染),useLayoutEffect 会抛出警告(Warning: useLayoutEffect does nothing on the server)。因为在服务端没有 DOM 和 Paint 的概念,React 会自动降级为 useEffect