人人都会AI编程

useLayoutEffect:同步执行的副作用

更新时间:2026-07-11

它和 useEffect 有什么区别?

useEffect 是大多数情况下处理副作用的默认选择,它在浏览器完成布局与绘制之后异步执行,不会阻塞页面渲染。而 useLayoutEffect 会在 DOM 更新完成后、浏览器绘制之前同步执行,它会阻塞浏览器的绘制过程,直到副作用代码运行结束。

两者的调用时机可以用一条时间线清晰对比:

渲染触发 → React 更新 DOM → 浏览器计算布局
                                  ↓
                     useLayoutEffect 执行(同步,此时 DOM 已更新但屏幕未刷新)
                                  ↓
                         浏览器绘制屏幕(用户看到新 UI)
                                  ↓
                       useEffect 执行(异步,不阻塞绘制)

正是这一时机的差异,决定了它们的适用场景完全不同。

什么时候应该使用 useLayoutEffect?

useLayoutEffect 适合那些需要在浏览器绘制前同步读取或修改 DOM,以避免用户看到闪烁或不一致界面的场景。最常见的需求包括:

  • 测量 DOM 节点的尺寸或位置(如获取元素高度、滚动位置),然后根据测量结果同步调整界面。
  • 根据 DOM 状态立即修改样式,避免先渲染出一个错误的样式再迅速修正,造成视觉抖动。
  • 动画或过渡前的必要计算,让最终渲染的界面就是动画起始的正确状态。

说白了,如果你要根据某个 DOM 的真实尺寸或位置去更新 UI,而这些更新又会影响本次渲染的画面,那么就应该使用 useLayoutEffect,确保计算和修改在画笔落下前完成。

实际例子:修复闪烁的 Tooltip 定位

假设你要实现一个 Tooltip,它的位置需要根据触发元素的位置动态调整,确保不超出屏幕边界。如果使用 useEffect,Tooltip 会先出现在默认位置(比如元素正下方),再进行位置修正,用户会看到明显的“闪现一下”。

function Tooltip({ targetRef, children }) {
  const tooltipRef = useRef(null);
  const [position, setPosition] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    if (!targetRef.current || !tooltipRef.current) return;

    const targetRect = targetRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();

    // 根据目标元素和 Tooltip 的实际尺寸计算最终位置
    let top = targetRect.bottom + 8;
    let left = targetRect.left;

    // 避免右侧溢出
    if (left + tooltipRect.width > window.innerWidth) {
      left = window.innerWidth - tooltipRect.width - 8;
    }

    setPosition({ top, left });
  }, [targetRef]);

  return (
    <div
      ref={tooltipRef}
      style={{
        position: 'fixed',
        top: position.top,
        left: position.left,
      }}
    >
      {children}
    </div>
  );
}

由于 useLayoutEffect 在浏览器绘制前执行,Tooltip 会以正确的最终位置直接出现在屏幕上,不会有闪烁。如果换成 useEffect,用户会先看到 Tooltip 出现在错误位置(比如默认左上角),而后跳到正确位置。

使用时的注意事项

  1. 尽量不要在里面执行耗时操作:因为 useLayoutEffect 会阻塞绘制,内部如果有复杂计算或长任务,会导致页面卡顿感明显,表现为点击后界面“愣一下”才反应。这些耗时逻辑应该移到 useEffect 或 Web Worker 中处理。
  1. 大多数情况下不需要它:React 官方建议优先使用 useEffect,只在确实出现视觉闪烁或需要同步读取布局信息时才切换到 useLayoutEffect。过早优化或过度使用反而会损害性能。
  1. 服务端渲染(SSR)中没有意义useLayoutEffect 在服务端不会执行,而且在 SSR 阶段也无法访问真实的 DOM 布局。如果组件同时用于服务端和客户端,通常需要结合环境判断或退回到 useEffect,并接受可能的一次闪烁。
  1. 依赖数组与 useEffect 完全一致:同样需要正确声明依赖,避免遗漏造成闭包陷阱。清理函数的写法和执行时机也与 useEffect 一致(在组件卸载或依赖变化重新执行前运行)。

与 useEffect 的选用诀窍

一个简单有效的判断法则:如果副作用需要读取 DOM 布局信息,并且该信息会立刻影响这一次渲染的画面,使用 useLayoutEffect;否则一律使用 useEffect。当你发现部署后界面有微小闪烁时,再考虑将它改为 useLayoutEffect 即可。这个习惯可以帮你避免大部分性能陷阱。