React Hooks 编程:深入理解 useLayoutEffect 及其典型应用场景

122 阅读4分钟

在 React 开发中,我们经常需要操作 DOM 或者根据 DOM 布局进行一些视觉调整。这时候,你可能会遇到这样一个问题:

明明已经更新了状态,DOM 也发生了变化,但为什么获取不到最新的布局信息?

这就是我们今天要重点讲解的内容:useLayoutEffect 的作用与使用场景

为了更生动地说明这个问题,我们来一起看看一个台词渲染案例,以及一个弹窗居中的实际应用场景。


一、React 中的副作用 Hook 对比

1. useEffect

  • 执行时机:组件渲染完成之后(异步执行)
  • 用途
    • 数据请求
    • 订阅事件
    • 不依赖 DOM 精确布局的操作
useEffect(() => {
  console.log('useEffect: DOM 已更新');
}, []);

2. useLayoutEffect

  • 执行时机:DOM 更新后,页面绘制之前(同步执行)
  • 用途
    • 需要立即读取 DOM 布局信息(如 offsetHeight, getBoundingClientRect()
    • 同步修改样式避免“闪烁”
    • 弹窗定位、动画初始化等
useLayoutEffect(() => {
  console.log('useLayoutEffect: DOM 已更新,浏览器还没画');
}, []);

二、经典案例分析

📝 案例一:“野蛮女友”台词切换 + DOM 样式同步更新

function App() {
  const [content, setContent] = useState(`아니야, 내가 왜 네 말을 들어줘야 해?...`);
  const ref = useRef();

  useLayoutEffect(() => {
    setContent('曾经有一份真诚的爱情放在我面前...');
    ref.current.style.height = '200px';
  }, []);

  return (
    <div ref={ref} style={{ height: 100, background: 'LightBlue' }}>
      {content}
    </div>
  );
}

分析:

  • 使用 useLayoutEffect 是因为我们需要在 DOM 更新后立刻改变它的高度
  • 如果用 useEffect,可能在浏览器已经绘制旧高度后才修改样式,导致用户看到“跳动”的效果(俗称“闪烁”)。
  • useLayoutEffect 在绘制前同步执行,能确保用户看不到中间状态。

💥 案例二:弹窗垂直居中(关键布局操作)

function Modal() {
  const ref = useRef();

  useLayoutEffect(() => {
    const height = ref.current.offsetHeight;
    ref.current.style.marginTop = `${(window.innerHeight - height) / 2}px`;
  }, []);

  return (
    <div ref={ref} style={{ background: 'red', position: 'absolute', width: '200px' }}>
      我是弹窗
    </div>
  );
}

分析:

  • 弹窗显示前,必须先知道它的高度,才能计算出正确的 marginTop 实现居中。
  • 如果在 useEffect 中做这个操作,用户会先看到未居中的弹窗,然后突然跳到正确位置 —— 这就是“布局抖动”。
  • 使用 useLayoutEffect 可以在浏览器真正绘制前就完成居中计算,保证视觉上“一步到位”。

三、useLayoutEffect 能解决什么问题?

问题解决方案
页面元素“闪烁”或跳动useLayoutEffect 中同步修改样式
获取 DOM 最新尺寸失败useLayoutEffect 中读取布局属性
动画起始状态不一致利用 useLayoutEffect 提前计算初始值

四、useLayoutEffect 的局限性

虽然 useLayoutEffect 功能强大,但也有一些需要注意的地方:

  1. 阻塞页面渲染
    • 它是同步执行的,如果逻辑复杂,会影响首屏加载速度。
  2. 服务端渲染(SSR)兼容性差
    • SSR 时没有 DOM,访问 ref.current 会报错。
  3. 不要滥用
    • 大多数情况下优先使用 useEffect,只在必要时使用 useLayoutEffect

五、对比与使用建议

useEffectuseLayoutEffect 是 React 中两个非常相似但用途截然不同的副作用 Hook。它们的核心区别在于执行时机和对 DOM 操作的支持程度。

对比维度useEffectuseLayoutEffect
执行阶段组件渲染完成后(异步)DOM 更新后,浏览器绘制前(同步)
是否阻塞页面绘制❌ 不会✅ 会
能否读取最新 DOM 布局⚠️ 大多数情况下可以✅ 一定可以
推荐使用场景数据请求、事件监听等非布局操作获取 DOM 尺寸、同步样式修改、防止视觉抖动

使用建议:

  • 优先使用 useEffect:适用于大多数副作用逻辑,不会影响页面渲染性能。
  • 仅在必要时使用 useLayoutEffect:当你需要精确控制 DOM 布局、避免页面“闪烁”或跳动时,它才是首选。
  • 注意 SSR 兼容性问题:由于服务端没有 DOM 环境,在 SSR 场景中使用 useLayoutEffect 可能导致错误,建议降级为 useEffect 或进行环境判断。

六、结语

在前端开发的世界里,细节决定体验。

useLayoutEffect 虽然不是 React 中最常被使用的 Hook,但它却在某些特定场景下扮演着不可替代的角色。它让我们有机会在浏览器真正绘制页面之前,对 DOM 进行精确调整,从而避免视觉上的“抖动”与不一致。

但它的强大也伴随着代价:同步执行意味着阻塞渲染,稍有不慎就会影响性能,甚至拖慢首屏加载速度。因此,我们应当谨慎使用它,只在真正需要的时候才调用它。

这也提醒我们一个更广泛的技术原则:

越强大的工具,越需要克制地使用。