什么是 useEffect?
在 React 中,组件渲染后可能会需要执行一些操作,比如:
- 数据获取 (Data Fetching)
- 设置订阅或计时器
- 手动操作 DOM
- 添加事件监听器
这些操作在 React 中被称为"副作用"(side effects)。useEffect
钩子让你可以在函数组件中执行这些副作用。它在组件渲染后运行,并且可以让你控制它何时重新运行。
useEffect
的基本结构
useEffect
接受两个参数:
- 一个副作用函数(effect function):这是包含副作用逻辑的函数。
- 一个可选的依赖项数组(dependency array):它决定了副作用函数何时重新运行。
javascript
useEffect(() => {
// 你的副作用代码
// 例如:数据请求、设置事件监听器等
return () => {
// 可选的清理函数
// 例如:取消订阅、移除监听器等
};
}, [/* 依赖项数组 */]);
依赖项数组的三种情况
依赖项数组是控制 useEffect
行为的关键。
1. 没有依赖项数组
如果省略第二个参数,useEffect
将在每次组件渲染后都运行。
用例: 这是一个罕见 的用例。它会在任何状态或 props
改变时都执行副作用。这通常会导致性能问题或无限循环。
javascript
useEffect(() => {
console.log('每次渲染后都运行');
});
警告: 这通常不是你想要的效果,因为它会造成不必要的性能开销。
2. 空依赖项数组 []
如果传入一个空数组 []
,useEffect
将只在组件首次挂载时运行一次。
用例: 这是用于执行一次性设置或数据获取的常见模式。它与类组件中的 componentDidMount
生命周期方法类似。
ini
useEffect(() => {
console.log('组件已挂载');
// 例如:在此处进行初始数据获取
const fetchData = async () => {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
// ...
};
fetchData();
}, []); // 依赖项数组为空
3. 带有值的依赖项数组 [dep1, dep2]
如果依赖项数组包含一个或多个值,useEffect
将在组件首次挂载时运行,并且在依赖项中的任何一个值发生变化时再次运行。
用例: 当你需要根据某些 props
或 state
的变化来执行副作用时,这是最常用的模式。
javascript
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
console.log(`用户ID ${userId} 已改变,正在获取新数据`);
const fetchUserData = async () => {
const response = await fetch(`https://api.example.com/users/${userId}`);
const result = await response.json();
setUserData(result);
};
fetchUserData();
}, [userId]); // 当 userId 改变时,重新运行这个 effect
return (
<div>
{userData ? (
<pre>{JSON.stringify(userData, null, 2)}</pre>
) : (
<p>正在加载用户信息...</p>
)}
</div>
);
}
清理函数 (Cleanup Function)
有些副作用(如事件监听器、计时器或订阅)需要被清理,以防止内存泄漏。useEffect
的副作用函数可以返回一个清理函数。
React 会在组件卸载时,以及在下一次运行副作用函数之前,执行这个清理函数。
用例: 移除事件监听器或取消订阅。
javascript
useEffect(() => {
const handleScroll = () => {
console.log('正在滚动...');
};
window.addEventListener('scroll', handleScroll);
// 返回清理函数
return () => {
console.log('正在移除滚动监听器...');
window.removeEventListener('scroll', handleScroll);
};
}, []);
最佳实践和注意事项
- 遵循依赖项规则: 始终将
effect
函数中使用的所有props
、state
和函数都包含在依赖项数组中。ESLint 的react-hooks/exhaustive-deps
规则会对此进行检查,并给出警告。 - 使用
useCallback
和useMemo
: 如果你将一个函数或对象作为useEffect
的依赖项,并且它在每次渲染时都会被重新创建,那么useEffect
也会重复运行。在这种情况下,请使用useCallback
来缓存函数,或使用useMemo
来缓存对象,以确保依赖项的引用是稳定的。 - 拆分
useEffect
钩子: 如果你的组件有多个不相关的副作用,最好为每个副作用使用独立的useEffect
钩子。这能提高代码的可读性,并使依赖项管理更加清晰。