useEffect 和 useLayoutEffect 的区别?
执行时机
| Hook | 执行时机 | 阻塞渲染 | 适合场景 |
|---|---|---|---|
useEffect |
DOM 更新后异步执行 | ❌ | 数据请求、订阅、日志 |
useLayoutEffect |
DOM 更新后同步执行(绘制前) | ✅ | 读取/修改 DOM 布局 |
执行流程
状态变化 → React 渲染 → DOM 更新
→ useLayoutEffect 同步执行 ← 此时浏览器还未绘制
→ 浏览器绘制到屏幕
→ useEffect 异步执行 ← 此时用户已看到画面useLayoutEffect 使用场景
需要读取 DOM 布局并同步修改时使用,避免闪烁:
function Tooltip({ children }) {
const ref = useRef()
const [position, setPosition] = useState({ x: 0, y: 0 })
useLayoutEffect(() => {
// 读取 DOM 位置
const rect = ref.current.getBoundingClientRect()
// 同步更新位置,避免闪烁
setPosition({
x: rect.x + rect.width,
y: rect.y
})
}, [children])
return (
<div ref={ref} style={{ transform: `translate(${position.x}px, ${position.y}px)` }}>
{children}
</div>
)
}如果用 useEffect 会怎样
// ❌ 用户会先看到 tooltip 在错误位置,然后跳到正确位置(闪烁)
useEffect(() => {
const rect = ref.current.getBoundingClientRect()
setPosition({ x: rect.x + rect.width, y: rect.y })
}, [children])useEffect 常见用法
// 数据请求(异步,不阻塞渲染)
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => setData(data))
}, [])
// 事件监听
useEffect(() => {
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// 依赖变化重新执行
useEffect(() => {
if (userId) fetchUser(userId)
}, [userId])SSR 注意
useLayoutEffect 在 SSR 中会报警告(服务端没有 DOM),可用 useIsomorphicLayoutEffect 兼容:
const useIsomorphicLayoutEffect =
typeof window !== 'undefined' ? useLayoutEffect : useEffect