? ? ? 你以為它只是替代componentDidMount
?數據抓取、事件綁定、定時清理...?事實上,useEffect
才是函數組件的“幕后操控者”!但依賴數組的坑、閉包的陷阱,你真的玩轉了嗎?
告別“能用就行”,今天帶你徹底拆解核心邏輯,從優雅使用到精準避坑,解鎖真正的“精通”段位!
一、useEffect 基礎:揭開副作用這層神秘的面紗
簡單來說,副作用就是組件渲染之外的 “額外操作” ,比如:
發起網絡請求獲取數據
設置定時器或 Interval
添加 / 移除事件監聽
手動操作 DOM
? ? ? 這些操作不能直接寫在組件函數里(會阻塞渲染),而useEffect
就是 React 提供的 “副作用專屬容器”。
2. 基本語法與執行時機制
useEffect(() => {// 副作用邏輯(如數據獲取、事件監聽等)console.log('副作用執行');return () => {// 清理函數(組件卸載或更新前執行)console.log('清理副作用');};
}, [依賴數組]); // 可選,控制副作用何時重新執行
無依賴數組:每次組件渲染(掛載 + 更新)后都會執行,相當于
componentDidMount
?+?componentDidUpdate
空依賴數組
[]
:僅在組件掛載后執行一次,類似componentDidMount
指定依賴項:只有依賴項變化時才執行,比如
[count]
表示count
狀態變化時觸發
💡 注意:React 會在瀏覽器完成頁面渲染后異步執行useEffect
,不會阻塞用戶界面,這點和useLayoutEffect
的同步執行不同。
二、生命周期平替:useEffect 的 “三重身份”
1. 掛載階段:模擬 componentDidMount
? ? ? 當依賴數組為空時,useEffect
會在組件首次渲染后執行,適合做初始化操作:
useEffect(() => {console.log('組件掛載完成!');// 發起初始化數據請求fetchData();
}, []);
2. 更新階段:替代 componentDidUpdate
? ? ? 當依賴數組包含特定狀態 / Props 時,只有它們變化才會觸發副作用:
const [count, setCount] = useState(0);useEffect(() => {console.log(`count更新為:${count}`);
}, [count]); // 僅count變化時執行
3. 卸載階段:實現 componentWillUnmount
? ? ? 通過返回清理函數,在組件卸載前執行資源釋放操作:
useEffect(() => {const timer = setInterval(() => {setCount(prev => prev + 1);}, 1000);return () => {clearInterval(timer); // 清除定時器,避免內存泄漏console.log('組件卸載,定時器已清除');};
}, []);
🎯 要點:清理函數會在組件卸載時執行,也會在下次同 effect 執行前執行,確保副作用 “有始有終”。
三、實戰場景:用 useEffect 解決真實問題
1. 數據獲取:接口請求的正確姿勢
? ? ? 內部定義 async 函數
useEffect(() => {const fetchData = async () => {const response = await fetch('https://api.example.com/data');const result = await response.json();setData(result);};fetchData(); // 立即執行異步函數
}, []); // 空依賴確保僅掛載時請求
2. 事件監聽:動態綁定與解綁
const [windowWidth, setWindowWidth] = useState(window.innerWidth);useEffect(() => {const handleResize = () => {setWindowWidth(window.innerWidth);};window.addEventListener('resize', handleResize); // 掛載時綁定事件return () => {window.removeEventListener('resize', handleResize); // 卸載時解綁};
}, []); // 僅綁定/解綁一次,性能更佳
3. 復雜場景:多個 effect 拆分關注點
function UserProfile({ userId }) {const [user, setUser] = useState(null);const [posts, setPosts] = useState([]);// 拆分不同副作用,邏輯更清晰useEffect(() => {// 獲取用戶信息fetchUser(userId).then(setUser);}, [userId]);useEffect(() => {// 獲取用戶帖子fetchPosts(userId).then(setPosts);}, [userId]);// ... 組件渲染邏輯
}
四、避坑指南:常見問題與最佳實踐
1. 依賴數組的 “精準控制”
- 不要遺漏必要依賴:ESLint 的
react-hooks/exhaustive-deps
規則能幫你檢測缺失的依賴項 - 避免冗余依賴:如果函數內部沒有使用某個狀態 / Props,就不要放進依賴數組
- 使用函數式更新:當副作用依賴前一次狀態時(如
setCount(prev => prev + 1)
),可以省略依賴項
2. 處理異步操作的內存泄漏
? ? ? 在數據請求場景中,組件可能在請求完成前卸載,此時更新狀態會導致報錯。解決方案:
useEffect(() => {let isMounted = true; // 標記組件是否仍掛載const fetchData = async () => {const data = await fetchData();if (isMounted) { // 確保組件未卸載時更新狀態setData(data);}};fetchData();return () => {isMounted = false; // 卸載時清除標記};
}, []);
3. 避免無限循環
? ? ? 當副作用內更新依賴的狀態時,可能觸發死循環:
// 解決方案:僅初始化時執行一次
useEffect(() => {setCount(0); // 初始值設置,空依賴避免重復執行
}, []);
五、代碼示例:完整組件中的 useEffect 應用
? ? ? 父組件 App.js(數據獲取 + 組件卸載清理)
import { useState, useEffect } from 'react';
import Timer from './Timer';function App() {const [repos, setRepos] = useState([]);const [isTimerOn, setIsTimerOn] = useState(true);// 僅在掛載時獲取GitHub倉庫數據useEffect(() => {const fetchRepos = async () => {const response = await fetch('https://api.github.com/users/shunwuyu/repos');const data = await response.json();setRepos(data);};fetchRepos();}, []);return (<div><h2>我的GitHub倉庫</h2><ul>{repos.map(repo => (<li key={repo.id}>{repo.full_name}</li>))}</ul><h3>定時器演示</h3>{isTimerOn && <Timer />}<button onClick={() => setIsTimerOn(!isTimerOn)}>切換定時器 {isTimerOn ? '關閉' : '開啟'}</button></div>);
}export default App;
子組件 Timer.js(定時器清理)
import { useState, useEffect } from 'react';function Timer() {const [time, setTime] = useState(0);useEffect(() => {const interval = setInterval(() => {setTime(prev => prev + 1); // 使用函數式更新,避免閉包問題}, 1000);return () => {clearInterval(interval); // 組件卸載時清除定時器console.log('定時器已清除,避免內存泄漏~');};}, []); // 空依賴,僅初始化時啟動定時器return <div>已運行 {time} 秒</div>;
}export default Timer;
六、總結:useEffect 的核心價值
統一生命周期:一個 Hook 搞定掛載、更新、卸載三個階段邏輯
精準控制:依賴數組讓副作用 “按需執行”,避免不必要的性能損耗
函數式風格:配合
useState
等 Hook,讓函數組件擁有媲美類組件的能力,代碼更簡潔易維護
駕馭副作用:useEffect 三維度思考模型
時機維度:?此操作應錨定于哪個生命周期節點?(掛載 / 更新 / 卸載)
依賴維度:?哪些狀態或屬性的變遷將觸發其執行?(精確定義響應式依賴項)
資源維度:?副作用是否遺留需清理的資源?(定時器、訂閱、異步任務)
透徹解析此模型,useEffect 方能從工具升華為你精準掌控副作用的 React 核心利器。