--- type: slide --- #### 5-3 維護資料流的連動:不要欺騙 hooks 的 dependencies #### 主講人: Jan --- 回顧 --- useEffect 的 dependencies 是一種效能的優化手段,而非模擬 function component 的生命週期 --- 欺騙 dependencies 會造成什麼問題? [code](https://codesandbox.io/p/sandbox/qr-code-5-3-1-forked-l6v92m?file=%2Fsrc%2FCounter.jsx%3A4%2C39) --- useEffect 流程 - 第一次進入,dependencies 會將 0 儲存起來,方便後續的比較,接著觸發 setCount(1) 導致 re-render - 第二次進入由於 dependencies 已被更新為 [1] ,所以會觸發 useEffect 中的函式 - 在 render 本次 effect function 之前會先執行上一次 render 版本的 cleanup function ---- ![image](https://hackmd.io/_uploads/rkc96qRE0.png) --- 我們應該永遠對 dependencies 抱持誠實,這是無論如何在任何情況下都應該保持的原則,不應該有任何例外 --- 函式型別的依賴 --- ![fetchdata1](https://hackmd.io/_uploads/H1pceVJB0.png) ---- 透過 setQuery 更新時,重新取得 API 的資料 [code](https://codesandbox.io/p/sandbox/affectionate-lena-p5lqzy?file=%2Fsrc%2FApp.js%3A8%2C32) ---- 不過這樣寫的話,我們對 `dependencies` 會不誠實,因為我們明明就有使用到外部的 `fetchData` 卻沒有監聽他的變化 --- 如果我們對 `dependencies` 誠實,受傷的卻是我們自己 ---- ![fetchdata2](https://hackmd.io/_uploads/rJqRgNkS0.png) ---- 雖然對 `dependencies` 誠實,但是效能優化永遠都會失敗,原因是因為 fetchData 每次在 render 時都會產生新的函式 [code](https://codesandbox.io/p/sandbox/useeffect-2-qkz8yt?file=%2Fsrc%2FApp.js%3A20%2C24) --- 以下介紹幾種解決方法 --- 1. 把函式整包移到 effect function 裡面 ---- ![fetch3](https://hackmd.io/_uploads/HJVRqyJrC.png) --- 但,我不想要將這個函式都放到 effect function 裡面 ---- 理由: 如果把整個函式都放到 effect function 裡面,那麼在其他地方都沒辦法使用該函式了 --- 解法1: 把跟 component 無關的流程,抽離 component ---- ![fetch4](https://hackmd.io/_uploads/HkqURy1BC.png) --- 解法2: 把 useEffect 依賴的函式以 useCallback 包起來 ---- 把 function 抽離 component 的確是個不錯的方法,但是如果要傳遞的參數過多,反而會降低可讀性,這時候我們可以考慮使用 `useCallback` --- 我們先來複習一下 `useCallback` ---- ![callbackj](https://hackmd.io/_uploads/rJj-jekBC.png) useCallback 接受兩個參數 - 要執行的函式 - dependencies 這邊要特別注意:雖然跟 `useEffet` 有點像,不過 `dependencies` 在 `useCallback` 中是必填的 --- 解法2: 把 useEffect 依賴的函式以 useCallback 包起來 ---- ![callbackj1](https://hackmd.io/_uploads/HkHyWZJB0.png) --- 以 linter 來輔助填寫 dependencies [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks) [VS Code plugin](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) --- effect dependencies 常見的錯誤 --- 常見誤用1: 在 function component 中模擬 ComponentDidMount ---- Q: 如果我們真的有個需求是進入畫面`只執行一次` call api 的方法,那麼該怎麼寫? ---- ![fetch](https://hackmd.io/_uploads/ryDuhM1BR.png) ---- 這個寫法貌似不會有問題,不過在 React 18 中有可能會在 Mount 階段執行兩次 (只有在嚴格模式跟開發環境會執行兩次) [code](https://codesandbox.io/p/sandbox/qr-code-5-3-6-forked-487jzs?file=%2Fsrc%2FApp.jsx%3A13%2C1) ---- 或許...從今以後你可以改成這樣寫 ---- ![callbackj2](https://hackmd.io/_uploads/ByOLZmkBR.png) ---- `useRef` 除了可以儲存 DOM element 之外,也很適合儲存與畫面沒有連動關係的跨 render 資料 範例中,我們就是透過 `isEffectCalledRef` 來判斷是否為第一次進入 --- 常見誤用2: 以 dependencies 來判斷執行時機 ---- Q: 如果有一個需求是當 `todos` 被更新時,才去執行 `count + 1`,這時候大家會怎麼寫? ---- ![callbackj3](https://hackmd.io/_uploads/BkbE_71SC.png) ---- 這樣寫的話,那就是對 dependencies 不誠實,因為我們在陣列中寫了 effect function 中沒有出現的 todos ---- ![callbackj4](https://hackmd.io/_uploads/rJKTi7kH0.png) --- 自我檢測 --- Q1: useEffect 的 `dependencies` 有三種寫法,分別代表什麼意思? --- Q2: 當被通知到需要 render 到 useEffect 中間發生了哪些事? `keyword`: useLayoutEffect, useEffect, cleanup --- Q3: 如果欺騙了 `useEffect` 的 `dependencies` 會有哪些問題? --- Q4: 聽完今天的導讀,你會更改原本的寫法嗎?還是會照舊?