在 React 中,useEffect 和 useLayoutEffect 都是用来处理副作用的 Hook,但它们的执行时机和使用场景有显著区别。本文将重点讲解 useLayoutEffect 的作用、执行时机、与 useEffect 的区别,以及它能解决的实际问题。
什么是副作用?
在编程中,副作用(Side Effect)是指 一个函数除了返回值之外,对外部环境产生的任何影响。
通俗点说:
副作用就是“除了计算结果之外,还干了别的事”。
🧪 常见的副作用有哪些?
以下是一些常见的副作用例子:
| 类型 | 示例 |
|---|---|
| ✅ 数据获取 | 从服务器请求数据(如 fetch、axios) |
| ✅ 订阅事件 | 添加事件监听器(如 window.addEventListener) |
| ✅ 手动操作 DOM | 直接修改 DOM 元素的样式、位置、内容等 |
| ✅ 设置定时器 | setTimeout、setInterval |
| ✅ 清理工作 | 在组件卸载时取消订阅、清除定时器等 |
| ✅ 路由跳转 | 使用 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 是什么?
useLayoutEffect 与 useEffect 功能类似,但最大的不同是它的 执行时机更早,它会在 DOM 更新之后、浏览器绘制之前 执行。
📌 useLayoutEffect 的执行时机:
- 在组件渲染完成之后
- 在 DOM 更新之后
- 在浏览器真正绘制页面之前
- 是 同步执行 的,会阻塞浏览器绘制
- 可以在副作用中读取最新的 DOM 布局信息(如宽高、位置等)
举个例子:
useLayoutEffect(() => {
console.log('useLayoutEffect 执行了');
});
此时,DOM 已经更新,但用户还看不到变化,因为浏览器还没绘制,React 会等待 useLayoutEffect 执行完才会让页面显示出来。
三、useEffect 与 useLayoutEffect 的对比
| 特性 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | 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 更新后、浏览器绘制前执行,能有效防止页面“闪烁”、实现同步样式更新,是处理视觉一致性问题的利器。