前言
在 React 开发中,我们经常需要处理副作用(side effects)。React 提供了两个主要的 Hook 来处理副作用:useEffect
和 useLayoutEffect
。虽然它们看起来很相似,但在使用时机和使用场景上有着重要区别。本文将重点介绍 useLayoutEffect
,探讨它的工作原理、使用场景以及如何利用它解决常见的 UI 问题。

useEffect 回顾
在深入 useLayoutEffect
之前,让我们先快速回顾一下 useEffect
:
js
useEffect(() => {
// 副作用代码
return () => {
// 清理函数
};
}, [dependencies]);
可以看到,useEffect
的特点:
- 在组件渲染完成后异步执行
- 不会阻塞浏览器的绘制(paint)
- 适用于大多数副作用场景(数据获取、订阅等)
useLayoutEffect 详解
API的用法
useLayoutEffect
的 API的用法如下:
js
useLayoutEffect(() => {
// 副作用代码
return () => {
// 清理函数
};
}, [dependencies]);
你会发现,其API的使用几乎和useEffect是完全相同的
执行阶段与特性
对于useLayoutEffect
,理解其执行阶段和特性是最重要的
-
执行阶段:
- 在 DOM 更新之后
- 在浏览器计算布局(Layout)之后
- 在浏览器实际绘制(Paint)屏幕之前
-
特性:
- 同步执行,会阻塞浏览器的绘制
- 适合需要同步读取或修改 DOM 的场景
- 可以避免视觉上的"闪烁"或布局跳动
React的生命周期
理解 React 组件的生命周期有助于我们更好地把握这两个 Hook 的区别:
React的生命周期:
- 组件渲染(Render) - React 计算虚拟 DOM 变化
- DOM 更新 - React 将变化应用到真实 DOM
- 浏览器计算布局(Layout) - 浏览器计算元素的新位置和尺寸
- useLayoutEffect 执行 ← 在这里!
- 浏览器绘制(Paint) - 实际将像素绘制到屏幕
- useEffect 执行
典型使用场景
1. 避免布局"闪烁"
什么是布局闪烁?
布局闪烁是指当用户界面的元素在渲染后突然改变位置或尺寸,导致用户看到明显的跳动或闪烁效果。这种现象通常发生在 React 组件需要根据 DOM 元素的尺寸或位置进行动态调整时。
发生布局闪烁的原因是当我们使用传统的useEffect时,元素变化是在渲染之后进行的
看这个例子:
js
function App(){
const [content,setContent] = useState("11111111111111111111111111111111111111")
//使用useEffect时:
useEffect(() => {
setContent('2222222222222222222222222222222222')
},[])
return (
<div>{content}</div>
)
}
当使用useEffect时:
- 组件首次渲染,content 初始值为 "1111..."(长字符串1)
- React 将
<div>1111...</div>
提交给浏览器绘制 - 用户短暂看到 "1111..." 的内容
- useEffect 回调执行,触发 setContent('2222...')
- 组件重新渲染,显示
<div>2222...</div>
- 用户看到内容从 "1111..." 变为 "2222..."(闪动过程)
现代浏览器和设备很高级,导致闪动的速度很快,你可能观察不到这个效果,但是在一些早期性能较低的设备中,对于页面中的一大堆内容,闪烁会给用户带来不好的用户体验
所以我们需要使用 useLayoutEffect
,这样做可以确保这些调整在用户看到屏幕之前完成,因此优化闪烁效果
js
function App(){
const [content,setContent] = useState("11111111111111111111111111111111111111")
//使用useLayoutEffect时:
useLayoutEffect(() => {
setContent('2222222222222222222222222222222222')
},[])
return (
<div>{content}</div>
)
}
这里的过程是这样的:
- 组件首次渲染,content 初始值为 "1111..."
- 在浏览器绘制前,useLayoutEffect 回调执行,设置 content 为 "2222..."
- 组件立即重新渲染,准备显示
<div>2222...</div>
- 浏览器绘制最终结果
- 用户直接看到 "2222...",没有看到中间状态
这种过程你可以理解为useLayoutEffect是和渲染同步进行的,因此就不会出现闪烁效果!
2. 同步获取 DOM 属性
useLayoutEffect还有一种常见的应用场景就是,当需要同步读取 DOM 并立即基于读取的值进行更新时:
js
function AutoHeightTextarea() {
const textareaRef = useRef(null);
const [height, setHeight] = useState('auto');
useLayoutEffect(() => {
// 同步获取滚动高度并设置
setHeight(`${textareaRef.current.scrollHeight}px`);
}, [value]);
return (
<textarea
ref={textareaRef}
style={{ height }}
value={value}
onChange={handleChange}
/>
);
}
在这个例子中,为什么必须使用 useLayoutEffect?
-
同步性需求:
- 我们需要在浏览器绘制前确保文本框高度已经调整
- 任何延迟都会导致用户看到文本框高度变化的"跳跃"效果
-
DOM 测量与更新的原子性:

性能考虑
由于 useLayoutEffect
会阻塞浏览器绘制,不当使用可能导致性能问题。遵循以下准则:
- 仅在必要时使用 - 大多数情况下,
useEffect
是更好的选择 - 保持轻量 - 在
useLayoutEffect
中执行的操作应该尽可能快速 - 避免频繁触发 - 谨慎设置依赖项,避免不必要的执行
两者区别总结
特性 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 异步,不阻塞绘制 | 同步,阻塞绘制 |
适用场景 | 大多数副作用 | DOM 测量和同步更新 |
服务端渲染 | 正常工作 | 会触发警告 |
性能影响 | 较小 | 可能较大(如果滥用) |
何时使用 useLayoutEffect:
- 需要同步读取或修改 DOM 布局时
- 需要避免视觉上的"闪烁"或布局跳动时
- 在动画初始化或复杂交互场景中
总结
现在我相信你已经能够入门useLayoutEffect的基本概念、使用方法与应用场景了,如果想要了解更多的关于useLayoutEffect的详细知识,推荐可以去看React的官方文档