一、核心概念:什么是 useEffect?
首先,先理解兩個(gè)關(guān)鍵概念:
首先,先理解兩個(gè)關(guān)鍵概念:
副作用(Side Effect):指組件渲染完成后需要執(zhí)行的操作,比如:
數(shù)據(jù)請(qǐng)求(接口調(diào)用)
DOM 操作(修改 DOM 樣式、添加事件監(jiān)聽(tīng))
訂閱 / 取消訂閱(定時(shí)器、WebSocket 連接)
手動(dòng)修改 state(非通過(guò) setState)
useEffect 作用:替代類組件中的 componentDidMount、componentDidUpdate、componentWillUnmount 三個(gè)生命周期方法,統(tǒng)一處理副作用邏輯。
二、基本語(yǔ)法
import { useEffect } from 'react';
function MyComponent() {
// 基礎(chǔ)用法
useEffect(() => {
// 副作用邏輯(渲染后執(zhí)行)
// 可選的清理函數(shù)(組件卸載/下一次effect執(zhí)行前執(zhí)行)
return () => {
// 清理操作(比如清除定時(shí)器、取消訂閱)
};
}, [依賴項(xiàng)數(shù)組]); // 依賴項(xiàng):控制effect何時(shí)重新執(zhí)行
}
三、不同用法場(chǎng)景(核心重點(diǎn))
場(chǎng)景 1:無(wú)依賴項(xiàng)數(shù)組 → 每次渲染后都執(zhí)行
相當(dāng)于類組件的 componentDidMount + componentDidUpdate。
import { useState, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
// 每次組件渲染(count變化)后執(zhí)行
useEffect(() => {
console.log(`當(dāng)前count: ${count}`); // 渲染后打印count
document.title = `點(diǎn)擊了${count}次`; // 修改DOM(副作用)
}); // 無(wú)依賴項(xiàng)數(shù)組
return (
<button onClick={() => setCount(count + 1)}>
點(diǎn)擊次數(shù):{count}
</button>
);
}
執(zhí)行時(shí)機(jī):組件首次渲染后執(zhí)行,每次狀態(tài)更新(count 變化)重新渲染后也執(zhí)行。
場(chǎng)景 2:依賴項(xiàng)為空數(shù)組 → 僅首次渲染執(zhí)行
相當(dāng)于類組件的 componentDidMount,適合只需要執(zhí)行一次的操作(比如初始化請(qǐng)求、添加全局監(jiān)聽(tīng))。
import { useEffect } from 'react';
function UserList() {
useEffect(() => {
// 僅首次渲染時(shí)請(qǐng)求數(shù)據(jù)(只執(zhí)行一次)
const fetchUsers = async () => {
const res = await fetch('https://api.example.com/users');
const data = await res.json();
console.log('用戶數(shù)據(jù):', data);
};
fetchUsers();
// 清理函數(shù):組件卸載時(shí)執(zhí)行(比如取消請(qǐng)求)
return () => {
console.log('組件卸載,清理資源');
};
}, []); // 空依賴數(shù)組
return <div>用戶列表</div>;
}
執(zhí)行時(shí)機(jī):僅組件首次渲染完成后執(zhí)行一次,清理函數(shù)在組件卸載時(shí)執(zhí)行。
場(chǎng)景 3:依賴項(xiàng)數(shù)組包含特定值 → 僅依賴項(xiàng)變化時(shí)執(zhí)行
相當(dāng)于類組件的 componentDidUpdate(僅監(jiān)聽(tīng)指定狀態(tài)),是最常用的場(chǎng)景。
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// 僅當(dāng)userId變化時(shí),重新請(qǐng)求用戶信息
useEffect(() => {
const fetchUser = async () => {
const res = await fetch(`https://api.example.com/users/${userId}`);
const data = await res.json();
setUser(data);
};
fetchUser();
// 清理函數(shù):下一次effect執(zhí)行前/組件卸載時(shí)執(zhí)行
return () => {
console.log(`停止請(qǐng)求userId: ${userId}的信息`);
};
}, [userId]); // 依賴項(xiàng):userId
if (!user) return <div>加載中...</div>;
return <div>用戶名:{user.name}</div>;
}
執(zhí)行時(shí)機(jī):首次渲染執(zhí)行 + 每次 userId 變化時(shí)重新執(zhí)行,清理函數(shù)會(huì)在 “下一次 effect 執(zhí)行前” 先執(zhí)行。
四、關(guān)鍵注意事項(xiàng)
1.清理函數(shù)的必要性:
比如定時(shí)器如果不清理,組件卸載后仍會(huì)執(zhí)行,導(dǎo)致內(nèi)存泄漏:
useEffect(() => {
const timer = setInterval(() => {
console.log('定時(shí)器執(zhí)行');
}, 1000);
// 清理函數(shù):清除定時(shí)器
return () => clearInterval(timer);
}, []);
2.依賴項(xiàng)的正確性:
useEffect 內(nèi)部用到的變量(如 state、props、函數(shù))都必須加入依賴項(xiàng)數(shù)組,否則會(huì)捕獲舊值(閉包問(wèn)題)。
可以用 ESLint 規(guī)則 react-hooks/exhaustive-deps 自動(dòng)檢查依賴項(xiàng)是否完整。
3.effect 是同步的:
不要在 useEffect 外層加 async/await(會(huì)導(dǎo)致返回值變成 Promise,而非清理函數(shù)),正確寫法是在內(nèi)部定義異步函數(shù):
// 錯(cuò)誤寫法
// useEffect(async () => { ... }, []);
// 正確寫法
useEffect(() => {
const asyncFn = async () => { ... };
asyncFn();
}, []);
總結(jié)
- useEffect 是 React 函數(shù)組件處理副作用的核心 Hook,替代類組件的三個(gè)生命周期方法。
- 依賴項(xiàng)數(shù)組決定執(zhí)行時(shí)機(jī):
- 無(wú)數(shù)組 → 每次渲染執(zhí)行;
- 空數(shù)組 → 僅首次渲染執(zhí)行;
- 有值數(shù)組 → 依賴項(xiàng)變化時(shí)執(zhí)行。
- 清理函數(shù)用于釋放資源(定時(shí)器、訂閱、請(qǐng)求),避免內(nèi)存泄漏,執(zhí)行時(shí)機(jī)是 “組件卸載前” 或 “下一次 effect 執(zhí)行前”