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 回调在节点挂载时读取。真需要「量了再画」的场合不多,遇到了再上它。
useLayoutEffect
https://www.choria.top/posts/hook-use-layout-effect/分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 190 天
部分内容可能已过时