在 React 开发中,我们经常需要操作 DOM 或者根据 DOM 布局进行一些视觉调整。这时候,你可能会遇到这样一个问题:
明明已经更新了状态,DOM 也发生了变化,但为什么获取不到最新的布局信息?
这就是我们今天要重点讲解的内容:useLayoutEffect 的作用与使用场景。
为了更生动地说明这个问题,我们来一起看看一个台词渲染案例,以及一个弹窗居中的实际应用场景。
一、React 中的副作用 Hook 对比
1. useEffect
- 执行时机:组件渲染完成之后(异步执行)
- 用途:
- 数据请求
- 订阅事件
- 不依赖 DOM 精确布局的操作
useEffect(() => {
console.log('useEffect: DOM 已更新');
}, []);
2. useLayoutEffect
- 执行时机:DOM 更新后,页面绘制之前(同步执行)
- 用途:
- 需要立即读取 DOM 布局信息(如
offsetHeight,getBoundingClientRect()) - 同步修改样式避免“闪烁”
- 弹窗定位、动画初始化等
- 需要立即读取 DOM 布局信息(如
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 功能强大,但也有一些需要注意的地方:
- 阻塞页面渲染
- 它是同步执行的,如果逻辑复杂,会影响首屏加载速度。
- 服务端渲染(SSR)兼容性差
- SSR 时没有 DOM,访问
ref.current会报错。
- SSR 时没有 DOM,访问
- 不要滥用
- 大多数情况下优先使用
useEffect,只在必要时使用useLayoutEffect。
- 大多数情况下优先使用
五、对比与使用建议
useEffect 和 useLayoutEffect 是 React 中两个非常相似但用途截然不同的副作用 Hook。它们的核心区别在于执行时机和对 DOM 操作的支持程度。
| 对比维度 | useEffect | useLayoutEffect |
|---|---|---|
| 执行阶段 | 组件渲染完成后(异步) | DOM 更新后,浏览器绘制前(同步) |
| 是否阻塞页面绘制 | ❌ 不会 | ✅ 会 |
| 能否读取最新 DOM 布局 | ⚠️ 大多数情况下可以 | ✅ 一定可以 |
| 推荐使用场景 | 数据请求、事件监听等非布局操作 | 获取 DOM 尺寸、同步样式修改、防止视觉抖动 |
使用建议:
- 优先使用
useEffect:适用于大多数副作用逻辑,不会影响页面渲染性能。 - 仅在必要时使用
useLayoutEffect:当你需要精确控制 DOM 布局、避免页面“闪烁”或跳动时,它才是首选。 - 注意 SSR 兼容性问题:由于服务端没有 DOM 环境,在 SSR 场景中使用
useLayoutEffect可能导致错误,建议降级为useEffect或进行环境判断。
六、结语
在前端开发的世界里,细节决定体验。
useLayoutEffect 虽然不是 React 中最常被使用的 Hook,但它却在某些特定场景下扮演着不可替代的角色。它让我们有机会在浏览器真正绘制页面之前,对 DOM 进行精确调整,从而避免视觉上的“抖动”与不一致。
但它的强大也伴随着代价:同步执行意味着阻塞渲染,稍有不慎就会影响性能,甚至拖慢首屏加载速度。因此,我们应当谨慎使用它,只在真正需要的时候才调用它。
这也提醒我们一个更广泛的技术原则:
越强大的工具,越需要克制地使用。