在 React 的函数组件中,副作用的处理离不开两个核心 Hook:useEffect
和 useLayoutEffect
。虽然它们看起来相似,但在执行时机、使用场景和对渲染的影响方面存在显著差异。
🧠 什么是副作用(Side Effect)
副作用是指任何与组件渲染无关、且可能影响外部世界的操作,例如:
-
DOM 操作
-
订阅 / 清除订阅
-
请求接口
-
设置定时器
-
日志打印
React 为此提供了两个 Hook:useEffect
和 useLayoutEffect
。
🔁 useEffect:渲染后执行
🔧 执行时机:
useEffect
在 浏览器完成绘制之后 异步执行。这意味着:
useEffect 总是在屏幕更新后执行,不会阻塞浏览器渲染。
📌 语法:
scss
useEffect(() => {
// 副作用逻辑
return () => {// 可选:清理函数 };
},[deps]
);
🧪 示例:
javascript
useEffect(() => {
console.log('组件挂载或依赖变化后执行');
return () => { console.log('组件卸载或依赖变化前清理'); };
}, [value]);
✅ 适合场景:
-
请求数据(fetch)
-
添加订阅 / 事件监听
-
设置定时器
-
修改状态
-
本地存储操作(localStorage)
⚡️ useLayoutEffect:DOM 更新前执行
🔧 执行时机:
useLayoutEffect
在 DOM 变更后、浏览器绘制前 同步执行。也就是说:
它会阻塞浏览器绘制,确保你能在用户看到页面之前同步更新 DOM。
📌 语法相同:
scss
useLayoutEffect(() => {
// 同步副作用
return () => { // 清理操作 };
}, [deps]);
🧪 示例:
ini
useLayoutEffect(() => {
const width = ref.current?.offsetWidth;
console.log('在绘制前读取 DOM 宽度:', width);
}, []);
✅ 适合场景:
-
读取布局信息(如宽高、位置)
-
手动触发布局变更(如 scroll、focus)
-
与动画库协同使用(如 GSAP)
⏱ 执行时机对比图解
rust
render --> commit --> paint
↑ ↑
useLayoutEffect useEffect
-
useLayoutEffect
:在 commit 后立即执行(同步),阻塞绘制 -
useEffect
:在 paint 后异步执行,不会阻塞绘制
🚨 性能建议
React 官方建议:
优先使用
useEffect
,除非你必须在绘制前操作 DOM,才使用useLayoutEffect
。
原因是:
-
useLayoutEffect
可能阻塞页面渲染,影响首屏性能; -
在服务端渲染(SSR)中,
useLayoutEffect
会发出警告(推荐降级为useEffect
);
🎯 实战比较:防止闪烁 vs 非阻塞渲染
1️⃣ 使用 useEffect
(有可能先绘制再调整):
ini
useEffect(() => {
boxRef.current.style.transform = 'translateX(100px)';
}, []);
🔍 结果:初始会看到盒子在左边,然后才跳过去。
2️⃣ 使用 useLayoutEffect
(绘制前调整):
ini
useLayoutEffect(() => {
boxRef.current.style.transform = 'translateX(100px)';
}, []);
🔍 结果:页面一开始就看到正确的位置,没有跳动。
✅ 总结对比
特性 | useEffect |
useLayoutEffect |
---|---|---|
执行时机 | 浏览器绘制后(异步) | DOM 变更后,绘制前(同步) |
是否阻塞绘制 | 否 | 是 |
适合操作 | 网络请求、日志、订阅 | DOM 尺寸、同步动画、滚动位置 |
SSR 支持 | ✅ | ⚠️ 警告(不推荐) |
推荐使用 | 默认选择 | 仅限于必须同步操作的特殊情况 |
🧩 开发建议
-
✅ 优先使用
useEffect
-
⚠️ 只有在你必须读取 DOM 布局或阻止闪烁 时,才使用
useLayoutEffect
-
💡 如果你要做动画,优先考虑 CSS 或 requestAnimationFrame,再退而求其次使用
useLayoutEffect
📚 延伸阅读
-
React 官方文档:reactjs.org/docs/hooks-...
-
调度机制详解:React Fiber 架构原理