useEffect 和 useLayoutEffect 都是 React 的副作用 Hook,但它们最大的区别是:
执行时机不同。
一句话理解
-
useEffect👉 页面渲染完成后异步执行(不阻塞页面绘制)
-
useLayoutEffect👉 DOM 更新后、浏览器绘制前同步执行(会阻塞页面绘制)
React 渲染流程理解
React 更新页面大致流程:
1. React render
2. 更新 DOM
3. useLayoutEffect 执行
4. 浏览器绘制页面(paint)
5. useEffect 执行
所以:
useLayoutEffect 更早
useEffect 更晚
图解理解
useEffect
useEffect(() => {
console.log('effect')
}, [])
流程:
DOM更新
↓
页面先显示
↓
effect执行
用户会先看到页面。
useLayoutEffect
useLayoutEffect(() => {
console.log('layout effect')
}, [])
流程:
DOM更新
↓
layout effect执行
↓
页面显示
effect 执行完页面才显示。
核心区别(面试重点)
| 对比 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | 浏览器绘制后 | 浏览器绘制前 |
| 是否阻塞渲染 | 不阻塞 | 阻塞 |
| 执行方式 | 异步 | 同步 |
| 是否可能造成卡顿 | 不容易 | 容易 |
| 推荐程度 | 默认优先使用 | 特殊场景再用 |
使用场景
一、useEffect(99%场景)
这是最常用的。
适合:
-
请求接口
-
订阅事件
-
定时器
-
localStorage
-
数据获取
-
websocket
-
日志埋点
示例:
useEffect(() => {
fetchUser()
}, [])
或者:
useEffect(() => {
const timer = setInterval(() => {
console.log('timer')
}, 1000)
return () => clearInterval(timer)
}, [])
二、useLayoutEffect(操作 DOM)
只有当你:
"必须在页面绘制前修改 DOM"
才用它。
典型场景:
1. 获取 DOM 尺寸
因为此时 DOM 已更新。
useLayoutEffect(() => {
const width = divRef.current.offsetWidth
console.log(width)
}, [])
2. 防止页面闪烁(非常经典)
例如:
useEffect(() => {
div.style.left = '100px'
}, [])
会发生:
先显示原位置
再瞬移
用户能看到闪动。
而:
useLayoutEffect(() => {
div.style.left = '100px'
}, [])
浏览器绘制前就改好了。
用户看不到闪动。
3. 动画初始化
例如:
-
GSAP
-
Framer Motion
-
拖拽库
-
虚拟列表
很多库内部都用 useLayoutEffect。
4. 同步测量 + 修改 DOM
经典:
useLayoutEffect(() => {
const height = ref.current.offsetHeight
ref.current.style.height = height + 100 + 'px'
}, [])
因为:
测量DOM
+
修改DOM
必须同步完成。
为什么不要乱用 useLayoutEffect
因为它会:
阻塞浏览器绘制
如果里面:
-
请求接口
-
大计算
-
循环
-
await
都会导致:
白屏
卡顿
掉帧
所以 React 官方建议:
优先使用 useEffect。
只有"必须同步操作 DOM"才使用 useLayoutEffect。
一个经典面试题
useLayoutEffect 会不会阻塞页面渲染?
会。
因为:
它在浏览器 paint 前同步执行
所以会阻塞绘制。
useEffect 会不会?
不会。
因为:
页面已经绘制完了
它是异步调度。
React18 注意点
在 React18 严格模式下:
<React.StrictMode>
开发环境会:
执行两次 effect
包括:
-
useEffect
-
useLayoutEffect
这是为了检查副作用是否安全。
生产环境不会。
服务端渲染 SSR 注意
useLayoutEffect 在 SSR 中会警告:
useLayoutEffect does nothing on the server
因为:
服务端没有 DOM
很多库会写:
const useIsomorphicLayoutEffect =
typeof window !== 'undefined'
? useLayoutEffect
: useEffect
避免 SSR 报警告。
最佳实践(非常重要)
默认:
useEffect
只有:
需要同步操作DOM
才使用:
useLayoutEffect
面试总结版(背诵)
useEffect 和 useLayoutEffect 都用于处理副作用。
区别是:
-
useEffect在页面绘制后异步执行,不阻塞渲染,适合请求接口、订阅事件等普通副作用。 -
useLayoutEffect在 DOM 更新后、浏览器绘制前同步执行,会阻塞页面渲染,适合 DOM 测量、同步修改 DOM、防止闪烁等场景。
开发中应优先使用 useEffect,只有需要同步操作 DOM 时才使用 useLayoutEffect。