为什么你需要useLayoutEffect?深入理解同步副作用
在 React 的开发中,我们经常使用 useEffect
来处理副作用(如数据获取、订阅、手动 DOM 操作等)。然而,在某些特定的场景下,我们需要在 DOM 更新之后 、浏览器绘制之前 立即执行一些同步操作,这时就需要使用另一个 Hook ------ useLayoutEffect
。
一、useLayoutEffect
是什么?
useLayoutEffect
是 React 提供的一个 Hook,用于在组件渲染完成后执行副作用逻辑,但它会在浏览器重新绘制屏幕之前同步执行。这意味这你可以在页面未渲染之前就获取某个DOM节点的状态。
它的用法与 useEffect
几乎完全一致:
jsx
useLayoutEffect(() => {
// 同步执行的副作用逻辑
}, [依赖项]);
不同之处在于执行时机:
useEffect
:异步执行,在浏览器完成绘制后执行。useLayoutEffect
:同步执行,在 DOM 更新后、页面绘制前执行。
二、为什么需要 useLayoutEffect
?
React 的设计理念是异步和非阻塞的,大多数副作用都可以延迟到绘制之后执行。但在以下几种情况下,必须确保副作用在绘制前完成:
-
需要读取或修改 DOM 布局信息(如尺寸、位置)
例如你希望根据某个元素的实际大小来调整布局,如果使用
useEffect
,可能会导致视觉上的闪烁或重绘抖动。这是因为useEffect
是在页面渲染之后执行的,此时页面已经完成渲染并呈现给用户,这个时候useEffec
t执行修改样式的操作就会导致样式的快速变化,页面进行再一次的回流重绘从而造成闪烁或抖动jsxuseLayoutEffect(() => { const height = ref.current?.offsetHeight; if (height > 100) { setExpanded(false); } }, []);
-
避免视觉抖动(Layout Thrashing)
如果你在多个异步副作用中反复读写 DOM 尺寸,可能导致多次强制同步布局(Forced Synchronous Layout),影响性能和用户体验。
useLayoutEffect
可以确保所有计算在绘制前完成,避免这种问题。
三、useLayoutEffect
与 useEffect
的区别总结
特性 | useEffect |
useLayoutEffect |
---|---|---|
执行时机 | 渲染完成后异步执行(绘制后) | 渲染完成后同步执行(绘制前) |
是否阻塞绘制 | 否 | 是(可能影响性能) |
使用频率 | 推荐优先使用 | 仅在必要时使用 |
适合场景 | 数据请求、事件监听、非关键 DOM 操作 | 精确测量 DOM、防止布局抖动 |
四、实际使用场景示例
场景一:根据 DOM 尺寸进行布局调整
jsx
const MeasureBox = () => {
const boxRef = useRef(null);
const [size, setSize] = useState({ width: 0, height: 0 });
useLayoutEffect(() => {
if (boxRef.current) {
const { offsetWidth, offsetHeight } = boxRef.current;
setSize({ width: offsetWidth, height: offsetHeight });
}
}, []);
return (
<div>
<div ref={boxRef} style={{ width: '200px', height: '100px', background: 'lightblue' }}>
Box
</div>
<p>Width: {size.width}, Height: {size.height}</p>
</div>
);
};
场景二:实现精确动画起始状态
在进入动画前,你需要先获取 DOM 的初始位置,并设置动画起点:
jsx
useLayoutEffect(() => {
const element = ref.current;
if (element) {
const initialPosition = element.getBoundingClientRect().top;
animate(element, { top: initialPosition + 100 });
}
}, []);
五、注意事项
**推荐优先使用 useEffect
,**除非你明确需要在绘制前执行副作用,否则应优先使用 useEffect
,因为它不会阻塞浏览器绘制,对性能更友好。
**避免在 useLayoutEffect
中执行耗时操作,**由于它是同步执行的,如果在里面做大量计算或网络请求,会显著延长页面渲染时间,造成"白屏"或"卡顿"。
在 SSR(服务端渲染)中需谨慎使用,useLayoutEffect
在服务端无法执行,因为没有 DOM。如果你在 Next.js 或其他 SSR 框架中使用它,务必确保依赖的数据在客户端可用,或者在 useEffect
中降级处理。