写了个简单的布局:一个浅蓝色块默认高 50px,加载后要改成 200px 并居中。结果每次刷新都先看到矮块闪一下,再变成高块 ------ 明明就两行样式,咋就出了这种幺蛾子?
排查半天发现,罪魁祸首是我用错了钩子。今天就借着这个真实案例,聊聊 useEffect 和 useLayoutEffect 到底咋区分,以及为啥换个钩子就能解决闪烁问题。
先搞懂俩钩子到底啥时候干活?
不管是 useEffect 还是 useLayoutEffect,本质都是用来处理 "副作用" 的 ------ 比如操作 DOM、发请求、订阅事件这些。但它们干活的时间点,差了关键一步。
先简单说下 useEffect:它就像个 "慢性子",等页面渲染完、浏览器把内容画到屏幕上之后,才慢悠悠地执行。比如你改个 DOM 样式,它会等页面已经显示出来了再动手,所以有时候会看到 "先这样,再那样" 的跳变。
而 useLayoutEffect 是个 "急性子",它会在 DOM 更新完但还没画到屏幕上的时候就冲上去干活。相当于说:"等一下!先别画!让我改完这处再显示!" 所以它能保证你改的样式在屏幕上一次性到位,不会出现中间状态。
为啥会有 "闪烁"?用个案例说清楚
最典型的场景就是处理 DOM 样式的时候。比如你想让一个元素从 A 样式瞬间变成 B 样式,用不好就会闪一下。
我做了个简单的例子:
jsx
import {
useEffect,
useLayoutEffect,
useRef
} from 'react'
function Show(){
const ref = useRef(null)
useEffect(()=>{
const heightPx = 200; // 明确使用数值
ref.current.style.height = `${heightPx}px`;
ref.current.style.marginTop = `${(window.innerHeight - heightPx) / 2}px`;
},[])
return (
<div ref={ref} style={{height:'50px',background:'lightblue'}}>
内容
</div>
)
}
当你在浏览器快速刷新页面的时候,会看到明显的残影,也就是所谓的'闪烁'
换成 useLayoutEffect 试试:
jsx
// 阻塞渲染 同步的感觉
useLayoutEffect(()=>{
const heightPx = 200; // 明确使用数值
ref.current.style.height = `${heightPx}px`;
ref.current.style.marginTop = `${(window.innerHeight - heightPx) / 2}px`;
},[])
这次无论怎么刷新,都不会出现'闪烁效果'!因为 useLayoutEffect 在浏览器把内容画到屏幕之前就改完了样式,相当于 "偷偷" 改好再展示,自然不会有中间状态。
核心区别就一句话
- useEffect:渲染完 → 浏览器画到屏幕上 → 再执行(异步,不阻塞渲染)
- useLayoutEffect:渲染完 → 改 DOM → 浏览器再画(同步,会阻塞渲染)
简单说,useLayoutEffect 是 "赶在显示前插队改样式",useEffect 是 "显示完了再慢慢改"。
啥时候用哪个?
大多数时候用 useEffect 就行,毕竟不阻塞渲染,性能更好。但如果遇到这些情况,就得请 useLayoutEffect 出场了:
- 操作 DOM 样式时出现闪烁(比如上面的例子)
- 需要 "同步" 拿到 DOM 更新后的状态(比如获取元素宽高后立即调整位置)
不过要注意,useLayoutEffect 里别写太耗时的代码,不然会卡住页面 ------ 它可是会等你执行完才让浏览器画画的。
最后再总结下:useEffect 是 "佛系处理",useLayoutEffect 是 "急着搞定"。记住它们干活的时间点,以后遇到样式闪烁的坑,就知道该找谁帮忙啦~ 你们平时用这俩钩子的时候,还遇到过啥有意思的问题?评论区交流下呗~