useLayoutEffect和useEffect有什么区别?
在 React 中,useEffect
和 useLayoutEffect
是两个用于处理副作用的 Hook。虽然它们在用法上相似,但在执行时间和适用场景上有显著的区别。以下是对这两个 Hook 的详细比较和解释。
1. 基本概念
useEffect
- 定义:
useEffect
是一个 Hook,用于在组件渲染后执行副作用。它通常用于数据获取、订阅和手动 DOM 操作等场景。 - 执行时机:在组件渲染完成后,用户可以看到更新后的 UI。这意味着
useEffect
的执行是在浏览器绘制之后,可能会导致视觉更新的延迟。
useLayoutEffect
- 定义:
useLayoutEffect
也是一个 Hook,用于处理副作用。它的用途与useEffect
类似,但会在 DOM 更新后、浏览器绘制之前执行。 - 执行时机:在所有 DOM 变更完成后,但在浏览器绘制之前。这样,任何由
useLayoutEffect
产生的 DOM 变更都将在浏览器呈现之前完成,从而避免闪烁或不一致的 UI。
2. 执行顺序
useEffect
执行顺序
- 组件渲染。
- 浏览器绘制更新后的 UI。
- 执行
useEffect
中的副作用。
useLayoutEffect
执行顺序
- 组件渲染。
- 执行
useLayoutEffect
中的副作用。 - 浏览器绘制更新后的 UI。
示例
import React, { useEffect, useLayoutEffect } from 'react';
const Example = () => {
useEffect(() => {
console.log('useEffect 执行');
}, []);
useLayoutEffect(() => {
console.log('useLayoutEffect 执行');
}, []);
return <div>示例组件</div>;
};
export default Example;
在上面的示例中,useLayoutEffect
会在 DOM 更新后立即执行,而 useEffect
会等到浏览器绘制后才能执行。
3. 适用场景
使用 useEffect
的场景
- 数据获取:当需要从服务器获取数据并更新状态时。
- 事件监听:添加或移除事件监听器。
- 定时器:设置和清除定时器。
- 不影响布局的副作用:例如,更新状态不会影响到布局的副作用。
使用 useLayoutEffect
的场景
- 同步 DOM 读写:在需要读取 DOM 布局并立即写入的场景,例如在计算元素的宽度和高度时。
- 避免闪烁:在需要避免视觉闪烁的情况下,例如在动态调整样式或类名时。
- 动画:在进行动画时,需要在绘制之前进行 DOM 更新。
示例
使用 useEffect
进行数据获取
import React, { useEffect, useState } from 'react';
const DataFetchingComponent = () => {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
};
fetchData();
}, []);
return <div>{data ? JSON.stringify(data) : '加载中...'}</div>;
};
使用 useLayoutEffect
进行布局计算
import React, { useLayoutEffect, useRef } from 'react';
const LayoutEffectComponent = () => {
const divRef = useRef(null);
useLayoutEffect(() => {
const height = divRef.current.clientHeight;
console.log('当前高度:', height);
// 这里可以进行一些同步的 DOM 操作
}, []);
return <div ref={divRef}>这个组件的高度会被计算</div>;
};
4. 性能影响
useEffect
性能
- 性能较好:由于
useEffect
在浏览器绘制后执行,因此不会阻塞浏览器的绘制过程。这使得它在执行较长的副作用时不会影响用户体验。
useLayoutEffect
性能
- 可能影响性能:
useLayoutEffect
会阻塞浏览器的绘制过程,因此如果其中的副作用执行较慢,可能导致 UI 卡顿或延迟。要谨慎使用,确保它的副作用不会影响性能。
5. 清理副作用
两者都支持清理副作用的功能。返回一个清理函数可以在组件卸载或依赖项变化时执行清理。
示例
useEffect(() => {
const timer = setTimeout(() => {
console.log('定时器执行');
}, 1000);
return () => clearTimeout(timer); // 清理定时器
}, []);
useLayoutEffect(() => {
const resizeHandler = () => {
console.log('窗口大小变化');
};
window.addEventListener('resize', resizeHandler);
return () => {
window.removeEventListener('resize', resizeHandler); // 清理事件监听器
};
}, []);
6. 兼容性
useEffect
和useLayoutEffect
都在 React 16.8 及以上版本中可用。useLayoutEffect
在 SSR(服务端渲染)中不会执行,因此在 SSR 场景下使用时需谨慎。
7. 总结与选择
-
使用
useEffect
:- 对于不需要同步 DOM 更新的副作用。
- 进行数据获取、事件监听等操作。
- 适用于性能优化场景,不会阻塞 UI 渲染。
-
使用
useLayoutEffect
:- 进行需要同步读取和写入 DOM 的操作。
- 避免闪烁或需要立即反映布局变化的场景。
- 注意可能对性能产生影响,尽量在必要时使用。