# 問題
直接先看問題,原本照下方程式碼這樣子去調用 fetchCustomerData() 的話,會出現 React Hook useEffect has a missing dependency: 'fetchCustomerData'. Either include it or remove the dependency array. 的警告訊息

...
useEffect(() => {
fetchCustomerData();
}, [customerId]);
const fetchCustomerData = async () => {
try {
setLoading(true);
const [customerResponse, ordersResponse, transactionsResponse] = await Promise.all([
api.get<Customer>(`/customers/${customerId}/`),
api.get<Order[]>(`/customers/${customerId}/orders/`),
api.get<Transaction[]>(`/customers/${customerId}/transactions/`)
]);
setCustomer(customerResponse.data);
setOrders(ordersResponse.data);
setTransactions(transactionsResponse.data);
} catch (err: unknown) {
setError('無法取得客戶資料');
console.error('Error fetching customer:', err);
} finally {
setLoading(false);
}
};
...
如果不去理解的話,就會以為只要單純把 fetchCustomerData 放進 useEffect 的依賴陣列,並且將 fetchCustomerData 函數放在 useEffect 之前定義就可以解決問題
const fetchCustomerData = async () => {
console.log("fetchCustomerData called");
...
};
useEffect(() => {
fetchCustomerData();
}, [customerId, fetchCustomerData]);
當然很快的你就會發現 IDE 就會直接警告你會觸發無限 re-render 了,因為每次 fetchCustomerData 被調用時都會重新創建一個新的函式,又會觸發 useEffect 的依賴檢查,導致 useEffect 無限循環

原因:
- 第一次渲染:
- 創建
fetchCustomerData函數 (函數引用 = function#1) - useEffect 執行,依賴是 [customerId: 123, function#1]
- 創建
- useEffect 執行後觸發重新渲染:
- 重新創建
fetchCustomerData函數 (函數引用 = function#2) - React 比較依賴:[customerId: 123, function#2] ≠ [customerId: 123, function#1] - 因為函數引用不同,觸發 useEffect
- 重新創建
- 無限循環開始:
- 渲染 → 新函數 → useEffect 觸發 → 重新渲染 → 新函數 → useEffect 觸發 → ...
# 解決方法
使用 useCallback 包裝 fetchCustomerData 函數,並且指定 customerId 為依賴,這樣可以確保只有當 customerId 改變時才會重新創建函數,從而避免無限循環的問題。
const fetchCustomerData = useCallback(async () => {
...
}, [customerId]); // 只有當 customerId 改變時才重新建立函數
useEffect(() => {
fetchCustomerData();
}, [customerId, fetchCustomerData]);
# 補充
這也是技術面試中常會遇到的問題,比如會問 React 是如何判斷何時該重新渲染組件的,或是應該如何避免不必要的渲染等問題
這是就要好好複習基本觀念也就是 React 並不會深度比較物件或陣列的內容,而是只檢查它們的「記憶體位置」(也就是參考位址)有沒有改變。如果兩個物件內容一樣,但記憶體位置不同,React 會認為它們不一樣;反之,如果直接修改原本的物件內容而沒創造新物件,React 則會以為資料沒變,導致畫面不更新。
這也是為什麼在 React 中強調 「不可變資料(immutable data)」的原則 —— 只要資料變更,就應該建立一個新物件或新陣列,這樣 React 才能正確判斷有變化並重新渲染畫面。
// 每次渲染
const func1 = () => {};
const func2 = () => {};
console.log(func1 === func2); // false! 即使函數內容相同
console.log(123 === 123); // true (customerId
同樣概念也適用 useEffect 或 useCallback 等 Hook 的 依賴陣列中。如果你在每次渲染時都產生一個新的函式或物件,React 就會認為依賴變了,進而導致 effect 重新執行或元件重新渲染。因此正確管理依賴與避免不必要的重新建立,是提升效能的關鍵。
