535 字
3 分钟
useLayoutEffect

与 useEffect 只差时机#

一次更新的顺序是:

渲染 → 提交 DOM → useLayoutEffect → 浏览器绘制 → useEffect

签名完全相同,useLayoutEffect(effect, deps?),清理函数的规则也一样。唯一的差别是它跑在绘制之前,而且是同步的:它在跑完之前,浏览器不会画屏幕。

这个差别只在一种场景下有用:你要先量出真实布局,再据此改样式。

一个会闪的例子#

工具提示要贴在目标元素上方,位置得先量出来:

function Tooltip({ targetRef }) {
const tooltipRef = useRef(null)
const [top, setTop] = useState(0)
useLayoutEffect(() => {
const target = targetRef.current.getBoundingClientRect()
const height = tooltipRef.current.getBoundingClientRect().height
setTop(target.top - height - 4)
}, [targetRef])
return (
<div ref={tooltipRef} style={{ position: 'fixed', top }}>
提示
</div>
)
}

第一帧渲染时 top 还是 0,提示会画在页面顶部。useLayoutEffect 在绘制前把它改到正确位置,用户永远看不到错位那一帧。

把这里换成 useEffect,浏览器会先按 top: 0 画一帧,再跳到正确位置,肉眼能看到闪一下。这就是两者唯一的取舍点。

它会挡住绘制#

因为同步执行,里面放耗时操作会让页面卡住:用户看不到新内容,直到它跑完。

判断标准很简单:需要先读布局再改样式,用它;其余一律用 useEffect。请求数据、加事件监听、起计时器都不碰布局,用 useEffect,别让它挡住绘制。

服务端渲染时用不了#

服务端没有绘制阶段,它不会执行。组件如果要在服务端渲染,优先改用 useEffect;确实必须用它,就把它放在只在客户端渲染的组件里。

严格模式#

开发环境下同样会经历挂载、卸载、再挂载,Effect 因此跑两遍。清理函数写完整就不受影响。

大部分时候你不需要它#

组件里读到布局再改样式的需求,通常有更省事的做法:用 CSS 定位让浏览器自己算,或者用 ref 回调在节点挂载时读取。真需要「量了再画」的场合不多,遇到了再上它。

第 18 篇,共 39 篇本文属于系列

useLayoutEffect
https://www.choria.top/posts/hook-use-layout-effect/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时