🔁 useLayoutEffect:React 中用于精确控制 DOM 布局的副作用 Hook

90 阅读5分钟

在 React 中,useEffectuseLayoutEffect 都是用来处理副作用的 Hook,但它们的执行时机和使用场景有显著区别。本文将重点讲解 useLayoutEffect 的作用、执行时机、与 useEffect 的区别,以及它能解决的实际问题。

什么是副作用?

在编程中,副作用(Side Effect)是指 一个函数除了返回值之外,对外部环境产生的任何影响

通俗点说:

副作用就是“除了计算结果之外,还干了别的事”。

🧪 常见的副作用有哪些?

以下是一些常见的副作用例子:

类型示例
✅ 数据获取从服务器请求数据(如 fetchaxios
✅ 订阅事件添加事件监听器(如 window.addEventListener
✅ 手动操作 DOM直接修改 DOM 元素的样式、位置、内容等
✅ 设置定时器setTimeoutsetInterval
✅ 清理工作在组件卸载时取消订阅、清除定时器等
✅ 路由跳转使用 window.location 或 react-router 的 navigate
✅ localStorage 操作存储或读取本地数据

📝为什么需要特别关注副作用?

  • 副作用会影响应用的行为:它们可能会改变应用的状态或行为,如果不妥善管理,可能会导致意想不到的结果。
  • 副作用需要正确清理:比如定时器、事件监听器等,如果不清理,可能会造成内存泄漏或其他问题。

📝 如何管理副作用?

  • 使用 useEffect 或 useLayoutEffect 来定义和管理副作用。
  • 在 Hook 中返回一个清理函数,用于在组件卸载或下次副作用执行前清理资源。

一、useEffect

之前的有关文章:juejin.cn/post/752292…

在理解 useLayoutEffect 之前,我们先回顾一下 useEffect 的行为:

📌 useEffect 的执行时机:

  • 在组件渲染完成之后执行
  • 在 React 更新 DOM 并让 浏览器完成绘制之后执行
  • 是 异步执行 的,不会阻塞页面渲染
  • 更适合用于数据获取、事件订阅、计时器等不需要立即同步 DOM 的操作

举个例子:

useEffect(() => {
 console.log('useEffect 执行了');
});

用户会先看到界面更新,然后才在控制台看到输出

二、 useLayoutEffect 是什么?

useLayoutEffectuseEffect 功能类似,但最大的不同是它的 执行时机更早,它会在 DOM 更新之后、浏览器绘制之前 执行。

📌 useLayoutEffect 的执行时机:

  • 在组件渲染完成之后
  • 在 DOM 更新之后
  • 在浏览器真正绘制页面之前
  • 是 同步执行 的,会阻塞浏览器绘制
  • 可以在副作用中读取最新的 DOM 布局信息(如宽高、位置等)

举个例子:

useLayoutEffect(() => {
  console.log('useLayoutEffect 执行了');
});

此时,DOM 已经更新,但用户还看不到变化,因为浏览器还没绘制,React 会等待 useLayoutEffect 执行完才会让页面显示出来。

三、useEffect 与 useLayoutEffect 的对比

特性useEffectuseLayoutEffect
执行时机DOM 更新后,浏览器绘制DOM 更新后,浏览器绘制
是否阻塞页面渲染不阻塞阻塞
执行方式异步同步
能否读取最新 DOM 布局信息❌ 不推荐✅ 推荐
适合场景数据请求、订阅、非视觉副作用需要同步 DOM 布局的视觉副作用

四、为什么需要 useLayoutEffect

React 的设计初衷是让开发者无需关心 DOM 操作的细节,但在某些场景下,我们确实需要精确控制 DOM 的布局和样式useLayoutEffect 就是为了满足这种需求而设计的。

🎯 它能解决哪些问题?

1. 防止“闪烁”现象(UI 闪动)
场景描述:

你希望根据某个 DOM 元素的尺寸或位置,动态调整它的样式或布局。如果使用 useEffect,浏览器会先绘制旧的布局,再执行副作用并更新样式,这就会造成 页面“闪烁”或“抖动”

使用 useLayoutEffect 的优势:
  • 在绘制前就完成布局计算和更新
  • 用户看到的是最终布局,不会出现“先错后对”的视觉问题
示例代码:
useLayoutEffect(() => {
  const element = document.getElementById('box');
  const width = element.offsetWidth;
  if (width > 500) {
    element.style.backgroundColor = 'red';
  }
}, []);

2. 精确获取 DOM 布局信息
场景描述:

你需要获取某个元素的宽高、位置、滚动条位置等信息,并根据这些信息做同步调整。

为什么不能用 useEffect
  • useEffect 是异步的,等你拿到 DOM 的时候,页面已经渲染了,用户可能会看到布局变化的过程,造成视觉干扰。
为什么用 useLayoutEffect
  • 它在浏览器绘制前执行,可以同步获取并修改 DOM,用户只会看到最终结果。

3. 同步样式更新,避免视觉错乱
场景描述:

你希望在组件挂载后,立即根据某些状态调整样式,比如弹出层的位置、动画的起始点等。

示例:
useLayoutEffect(() => {
  const modal = document.getElementById('modal');
  modal.style.left = `${calculatePosition()}px`;
}, []);

这样用户看到的弹出层就是正确位置,不会出现“先闪到左边再跳到右边”的问题。

五、总结:如何选择 useEffect 和 useLayoutEffect

使用场景推荐 Hook
数据请求、事件监听、不需要同步 DOM 的副作用useEffect
需要读取 DOM 布局信息(宽高、位置等)useLayoutEffect
需要同步更新 DOM 样式,防止视觉“闪烁”useLayoutEffect
想避免阻塞页面渲染useEffect

 一句话总结:

useLayoutEffect 是 React 中用于处理需要同步 DOM 布局信息的 Hook,它在 DOM 更新后、浏览器绘制前执行,能有效防止页面“闪烁”、实现同步样式更新,是处理视觉一致性问题的利器。