React 中的副作用處理:effect 初探
什麼是 Effect
在 React 中,副作用 (Side Effect) 指的是在渲染元件 (Component) 時,執行一些與渲染無直接關係的操作。例如:
-
從伺服器獲取資料 (如 API 請求)
-
操作 DOM (如修改頁面標題)
-
設定計時器 (如 setTimeout 或 setInterval)
-
訂閱外部事件 (如 WebSocket 或事件監聽器)
這些操作不直接影響元件的渲染結果,但會影響應用程式的狀態或其他外部資源。React 提供了 useEffect Hook 來處理這些副作用,讓你可以在適當的時機執行它們。
簡單來說,Effect 就是 React 用來管理副作用的機制,確保副作用不會干擾元件的渲染流程。
React Component Function 中的副作用
在 React 的函數元件中,副作用通常不應該直接寫在元件的頂層,因為元件函數會在每次渲染時執行。如果直接在元件函數中執行副作用,可能導致以下問題:
-
重複執行:每次渲染時,副作用會重複運行,可能造成資源浪費或錯誤。
-
難以控制時機:無法精確控制副作用何時執行或清理。
不正確的副作用寫法
以下是一個錯誤的例子,直接在元件函數中執行副作用:
import React from "react";
function MyComponent() {
// 不正確:在元件頂層直接執行副作用
document.title = "正在載入..."; // 每次渲染都會執行
console.log("元件渲染了!");
return <div>Hello, World!</div>;
}
export default MyComponent;
問題:
-
每次元件渲染,
document.title
都會被重新設定。 -
如果有其他副作用(如 API 請求),可能會導致多次不必要的請求,影響效能。
解決方案:使用 useEffect Hook 來控制副作用的執行時機,避免在元件頂層直接執行。
useEffect 初探
useEffect 是 React 提供的一個 Hook,用來在函數元件中處理副作用。它允許你指定副作用的執行時機,並在需要時執行清理工作。
useEffect 基本語法
import { useEffect } from "react";
useEffect(() => {
// 副作用程式碼
}, [dependencies]);
-
第一個參數:一個回呼函數,包含要執行的副作用程式碼。
-
第二個參數:依賴陣列 (dependency array),控制副作用何時執行。如果陣列中的值改變,副作用會重新執行。
-
如果依賴陣列為空 ([]),副作用只在元件首次渲染時執行一次。
-
如果省略依賴陣列,副作用會在每次渲染時都執行。
-
範例:使用 useEffect 修改頁面標題
以下是一個簡單的 useEffect 範例,用來在元件渲染後修改頁面標題:
import React, { useEffect, useState } from "react";
function TitleUpdater() {
const [count, setCount] = useState(0);
// 使用 useEffect 處理副作用
useEffect(() => {
document.title = `你點擊了 ${count} 次`;
}, [count]); // 當 count 改變時,重新執行副作用
return (
<div>
<p>你點擊了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>點我</button>
</div>
);
}
export default TitleUpdater;
說明:
-
useEffect 確保
document.title
只在 count 改變時更新。 -
依賴陣列 [count] 表示副作用依賴於 count 狀態,當 count 改變時,副作用會重新執行。
執行時機
-
useEffect 中的回呼函數在元件渲染完成後執行(即 DOM 更新後)。
-
如果有依賴陣列,只有當陣列中的值改變時,副作用才會重新執行。
每次 Render 都有其自己版本的 Effect 函式
在 React 中,每次元件渲染都會產生一個新的 effect 函式。這意味著 useEffect 內的回呼函數會捕捉當前渲染時的 props 和 state 值。這種行為確保了副作用的邏輯與當前渲染的狀態一致。
範例:展示 Effect 捕捉當前狀態
import React, { useEffect, useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`當前 count 值: ${count}`);
}, [count]); // 每次 count 改變時,執行新的 effect
return (
<div>
<p>計數: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
export default Counter;
說明:
-
每次 count 改變,元件重新渲染,產生一個新的 useEffect 回呼函數。
-
新的回呼函數會捕捉當前的 count 值,並在控制台輸出。
-
這確保了副作用始終使用最新的狀態值。
注意:
-
如果依賴陣列中遺漏了某些依賴(如 count),可能導致副作用使用舊的狀態值,造成邏輯錯誤。
-
使用 ESLint 的 react-hooks/exhaustive-deps 規則可以幫助檢查依賴陣列是否完整。
每次 Render 都有其自己版本的 Cleanup 函式
useEffect 允許你返回一個 清理函數 (cleanup function),用來在副作用重新執行或元件卸載 (unmount) 前清理資源。每次渲染都會產生一個新的清理函數,對應當前的 effect 函數。
清理函數的用途
-
取消訂閱(如 WebSocket 或事件監聽器)。
-
清除計時器(如 setTimeout 或 setInterval)。
-
避免記憶體洩漏。
清理函數的執行時機
-
元件重新渲染:當依賴陣列中的值改變時,React 會先執行上一次的清理函數,再執行新的 effect 函數。
-
元件卸載:當元件從 DOM 中移除時,React 會執行最後一次的清理函數。
範例:使用 Cleanup 函數清除計時器
以下是一個使用 useEffect 設定計時器並清理的範例:
import React, { useEffect, useState } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
// 設定計時器
const timer = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
// 返回清理函數
return () => {
console.log("清理計時器");
clearInterval(timer); // 清除計時器
};
}, []); // 空依賴陣列,effect 只在首次渲染時執行
return (
<div>
<p>經過秒數: {seconds}</p>
</div>
);
}
export default Timer;
說明:
-
setInterval 每秒更新 seconds 狀態。
-
useEffect 返回的清理函數在元件卸載時執行,清除計時器,防止記憶體洩漏。
-
因為依賴陣列為空 [],effect 和清理函數只在元件掛載和卸載時各執行一次。
範例:依賴改變時的清理
如果依賴陣列不為空,每次依賴改變時,會先執行清理函數,再執行新的 effect:
import React, { useEffect, useState } from "react";
function UserProfile({ userId }) {
const [data, setData] = useState(null);
useEffect(() => {
console.log(`獲取 userId ${userId} 的資料`);
// 模擬 API 請求
const fetchData = async () => {
// 假設這是一個 API 請求
setData(`使用者 ${userId} 的資料`);
};
fetchData();
// 返回清理函數
return () => {
console.log(`清理 userId ${userId} 的資源`);
// 這裡可以取消 API 請求或清除其他資源
};
}, [userId]); // 當 userId 改變時,重新執行 effect
return <div>{data || "載入中..."}</div>;
}
export default UserProfile;
說明:
-
當 userId 改變時,React 會先執行上一次的清理函數(輸出清理訊息),再執行新的 effect(獲取新資料)。
-
這確保了舊的資源被正確清理,避免重複請求或記憶體洩漏。
總結
-
什麼是 Effect:Effect 是 React 用來處理副作用的機制,像是 API 請求、DOM 操作等。
-
元件中的副作用:不應直接在元件頂層執行副作用,應使用 useEffect 控制時機。
-
useEffect 初探:useEffect 是一個 Hook,用來在渲染後執行副作用,依賴陣列控制執行時機。
-
每次 Render 的 Effect:每次渲染產生新的 effect 函數,捕捉當前狀態和 props。
-
每次 Render 的 Cleanup:每次渲染產生新的清理函數,在依賴改變或元件卸載時執行,清理資源。
實作建議
-
始終正確設定依賴陣列,避免邏輯錯誤。
-
使用清理函數來防止記憶體洩漏,特別是涉及計時器或訂閱的情況。
-
如果不確定依賴陣列是否正確,使用 ESLint 的 react-hooks 插件檢查。