文章目录
前言
在 React 函数组件中,useEffect
是处理副作用(Side Effects)的核心 Hook。无论是数据获取、订阅事件、手动操作 DOM,还是其他异步任务,useEffect
都能帮助开发者优雅地管理这些副作用。然而,它的使用场景复杂且容易踩坑。本文将带你从基础到实战,全面掌握 useEffect
的核心原理和最佳实践。
一、为什么需要 useEffect
?
在类组件中,副作用通常通过生命周期方法(如 componentDidMount
、componentDidUpdate
、componentWillUnmount
)管理。但在函数组件中,由于没有生命周期方法,React 提供了 useEffect
来统一处理这些场景。
核心作用:
- 数据获取:在组件挂载后从 API 获取数据。
- 订阅/取消订阅:如 WebSocket、事件监听等。
- 手动操作 DOM:如调整滚动位置、聚焦输入框。
- 清理副作用:避免内存泄漏或重复操作。
二、useEffect
的基础用法
1. 基本语法
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// 副作用代码
return () => {
// 清理函数(可选)
};
}, [dependencies]); // 依赖项数组(可选)
return <div>...</div>;
}
2. 依赖项数组的作用
- 空数组:只在组件挂载时执行一次(类似
componentDidMount
)。 - 无依赖项:每次组件渲染后都会执行(类似
componentDidUpdate
,但容易引发性能问题)。 - 有依赖项:当依赖项变化时重新执行。
三、依赖项数组演示
1. 空数组 []
:
只在组件挂载时执行一次(类似 componentDidMount
)**
场景:组件挂载时执行一次初始化操作(如获取初始数据、订阅事件)。
import { useState, useEffect } from 'react';
function InitialDataLoader() {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchInitialData() {
const response = await fetch('/api/initial-data');
const result = await response.json();
setData(result);
}
fetchInitialData();
}, []); // 空数组:仅在组件挂载时执行
if (!data) return <div>Loading initial data...</div>;
return <div>{JSON.stringify(data)}</div>;
}
useEffect
仅在组件挂载时执行一次,适合初始化操作。- 清理函数(可选)通常用于取消订阅或清除资源(如
AbortController
)。
2.无依赖项(空)
无依赖项:每次组件渲染后都会执行(类似 componentDidUpdate
,但容易引发性能问题
场景:每次组件渲染后都执行某些操作(如记录日志、强制更新)。
注意:无依赖项的 useEffect
通常不推荐使用,除非明确需要每次渲染后执行。
import { useEffect } from 'react';
function LoggerComponent() {
useEffect(() => {
console.log('Component rendered or updated');
}); // 无依赖项:每次渲染后都会执行
return <div>Check the console for render logs.</div>;
}
- 每次组件渲染(包括状态更新、父组件更新等)后都会触发
useEffect
。 - 慎用:可能导致性能问题(如无限循环、频繁 API 调用)。
3.有依赖项
:当依赖项变化时重新执行
场景:依赖项(如状态、props)变化时重新执行副作用(如数据更新、重新订阅)。
import { useState, useEffect } from 'react';
function UserData({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
}
fetchUser();
}, [userId]); // 依赖项:userId 变化时重新执行
if (!user) return <div>Loading user data...</div>;
return <div>{user.name}</div>;
}
// 父组件使用示例
function App() {
const [currentUserId, setCurrentUserId] = useState(1);
return (
<div>
<button onClick={() => setCurrentUserId(2)}>Load User 2</button>
<UserData userId={currentUserId} />
</div>
);
}
- 当
userId
变化时(如点击按钮切换用户),useEffect
会重新执行,获取新用户数据。 - 依赖项可以是状态、props 或其他变量,确保副作用与组件状态同步。
四、清理副作用函数
在 React 的 useEffect 中,清理副作用函数(cleanup function)用于在组件卸载或依赖项变化时清除之前的副作用(如取消订阅事件、清除定时器、关闭网络请求等)。以下是几个常见的清理副作用函数的示例:
import { useState, useEffect } from "react";
function Child() {
useEffect(() => {
console.log("child 初始化");
return () => {
console.log("child 卸载了");
};
});
return <div>child</div>;
}
function App() {
const [count, setCount] = useState(0);
const [state, setState] = useState(true);
useEffect(() => {
return () => {
console.log("我执行了!!!");
};
}, [count]);
return (
<>
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<button onClick={() => setState(!state)}>显示/隐藏</button>
</div>
{state && <Child />}
</>
);
}
export default App;
实战案例演示
1. 清除定时器
场景:组件中使用了定时器,需要在组件卸载或依赖项变化时清除定时器。
import { useState, useEffect } from 'react';
function TimerComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
// 清理函数:清除定时器
return () => {
clearInterval(timer);
};
}, []); // 空数组:仅在组件挂载时设置定时;
return <div>Count: {count}</div>;
}
- 组件卸载时,
clearInterval(timer)
会被调用,避免内存泄漏。
2. 取消网络请求
场景:组件中发起了网络请求,需要在组件卸载或依赖项变化时取消请求。
import { useState, useEffect } from 'react';
function DataFetcher({ userId }) {
const [data, setData] = useState(null);
useEffect(() => {
const abortController = new AbortController();
async function fetchData() {
try {
const response = await fetch(`/api/users/${userId}`, {
signal: abortController.signal,
});
const result = await response.json();
setData(result);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Fetch error:', error);
}
}
}
fetchData();
// 清理函数:取消请求
return () => {
abortController.abort();
};
}, [userId]); // 依赖项:userId 变化时重新执行
if (!data) return <div>Loading...</div>;
return <div>{JSON.stringify(data)}</div>;
}
总结
useEffect 是 React 函数组件中管理副作用的强大工具,但需要开发者深入理解其工作原理和潜在问题。通过合理使用依赖项、清理函数和函数式更新,可以避免常见的陷阱,写出高效、稳定的代码。
希望本文能帮助你更好地掌握 useEffect,并在实际项目中灵活运用!如果有任何疑问或补充,欢迎在评论区留言讨论。 🚀