useEffect 和 useLayoutEffect 执行时机
想知道useEffect 和 useLayoutEffect 执行时机,首先必须了解React 组件更新生命周期
1、触发更新
- setState更新状态
- 父组件更新等
2、渲染阶段
- 调用组件函数生成虚拟DOM
- React进行虚拟DOM对比
3、提交阶段
- 应用更新DOM(实际修改浏览器DOM)
- 执行useLayoutEffect清理函数(如有)
- 执行useLayoutEffect副作用(如有)
4、浏览器绘制
- 浏览器将更新后的DOM渲染到屏幕上
5、useEffect阶段
- 执行useEffect函数清理(如有)
- 执行useEffect副作用(如有)
简单来说:
关键概念详解
1、浏览器绘制
浏览器绘制是什么:浏览器绘制是计算好的DOM结构和样式实际渲染到浏览器屏幕上的这个过程
- 计算css样式
- 生成布局
- 绘制像素
- 合成图层
2、同步 vs 异步
useLayoutEffect同步执行
// 伪代码表示React内部处理
function commitWork() {commitDOMUpdates(); // 更新DOMflushLayoutEffects(); // 立即执行所有useLayoutEffectrequestPaint(); // 通知浏览器可以绘制了
}
会阻塞浏览器绘制,直到所有useLayoutEffect执行完成
useEffect异步执行
function scheduleEffect() {afterPaint(() => { // 浏览器绘制后flushPassiveEffects(); // 执行useEffect});
}
通过requestIdleCallback或setTimeout等异步API调度
总结:
特性 | useEffect | useLayoutEffect |
---|---|---|
执行时机 | 浏览器绘制后异步执行 | DOM更新后、浏览器绘制前同步执行 |
可视化影响 | 可能看到闪烁/布局跳动 | 避免布局跳动 |
性能影响 | 不阻塞渲染 | 可能阻塞渲染 |
适用场景 | 数据获取、订阅等普通副作用 | 需要同步DOM操作的场景 |
场景使用推荐:
使用 useLayoutEffect 的场景
1、测试DOM元素
useLayoutEffect(() => {const rect = ref.current.getBoundingClientRect();setSize(rect);
}, []);
2、同步样式调整
useLayoutEffect(() => {tooltipRef.current.style.left = `${buttonRect.left}px`;
}, [buttonRect]);
3、第三方DOM库集成
useLayoutEffect(() => {thirdPartyLib.init(ref.current);return () => thirdPartyLib.destroy();
}, []);
使用 useEffect 的场景
1、数据获取
useEffect(() => {fetchData().then(setData);
}, []);
2、事件订阅
useEffect(() => {const sub = store.subscribe(handleChange);return () => sub.unsubscribe();
}, []);
3、非关键动画
useEffect(() => {const timer = setTimeout(() => {// 动画逻辑}, 100);return () => clearTimeout(timer);
}, []);
错误场景案例
会看到闪烁的例子,但使用了useEffect
function FlashingComponent() {const [width, setWidth] = useState(100);const divRef = useRef();useEffect(() => {// 在绘制后执行,用户会先看到0宽度,再看到100宽度divRef.current.style.width = '100px';}, []);return <div ref={divRef} style={{ width: 0, background: 'red' }} />;
}
正确的版本:使用useLayoutEffect
function StableComponent() {const divRef = useRef();useLayoutEffect(() => {// 在绘制前执行,用户直接看到最终效果divRef.current.style.width = '100px';}, []);return <div ref={divRef} style={{ width: 0, background: 'green' }} />;
}
为什么SSR中不能使用useLayoutEffect?
因为服务端渲染都没有DOM环境,使用会发出警告。可以先判断有没有dom
// 动态检测环境
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
如何判断是否需要useLayoutEffect?
主要判断自己的副作用是否会导致布局的变化,如果是则用useLayout,否则用useEffect
为什么react不默认使用useLayoutEffect?
因为同步执行会损害性能,所以react团队会建议默认使用useEffect,除非是需要同步任务执行