問題
直接先看問題,原本照下方程式碼這樣子去調用 fetchCustomerData()
的話,會出現 React Hook useEffect has a missing dependency: 'fetchCustomerData'. Either include it or remove the dependency array.
的警告訊息
1 | ... |
如果不去理解的話,就會以為只要單純把 fetchCustomerData
放進 useEffect 的依賴陣列,並且將 fetchCustomerData
函數放在 useEffect 之前定義就可以解決問題
1 | const fetchCustomerData = async () => { |
當然很快的你就會發現 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
改變時才會重新創建函數,從而避免無限循環的問題。
1 | const fetchCustomerData = useCallback(async () => { |
觀念補充
這也是在技術面試中常會遇到的問題,比如會問 React 是如何判斷何時該重新渲染組件的,或是應該如何避免不必要的渲染等問題
這是就要好好複習基本觀念也就是 React 並不會深度比較物件或陣列的內容,而是只檢查它們的「記憶體位置」(也就是參考位址)有沒有改變。如果兩個物件內容一樣,但記憶體位置不同,React 會認為它們不一樣;反之,如果直接修改原本的物件內容而沒創造新物件,React 則會以為資料沒變,導致畫面不更新。
這也是為什麼在 React 中強調 「不可變資料(immutable data)」的原則 —— 只要資料變更,就應該建立一個新物件或新陣列,這樣 React 才能正確判斷有變化並重新渲染畫面。
1 | // 每次渲染 |
同樣概念也適用 useEffect 或 useCallback 等 Hook 的 依賴陣列中。如果你在每次渲染時都產生一個新的函式或物件,React 就會認為依賴變了,進而導致 effect 重新執行或元件重新渲染。因此正確管理依賴與避免不必要的重新建立,是提升效能的關鍵。
若您覺得這篇文章對您有幫助,歡迎分享出去讓更多人看到⊂◉‿◉つ~
留言版