# React misunderstanding issues (Part 2) Study report : https://ithelp.ithome.com.tw/users/20129300/ironman/5892 --- ### React misunderstanding #### 6. useEffect ##### 6-1 design concepts :::warning **useEffect 的核心理念是同步資料,而非達到 class 裡生命週期的作用** ::: * React Hooks 的設計旨在解決在 class 寫法中出現的幾個常見問題,其中一個就是複雜的生命週期方法。 * useEffect 不在乎你是第幾次 render,因為他要執行的事情是一樣的;但 class 在乎(因此分成 mount 跟 update) - 宣告式程式設計的概念 - 只在乎結果,不在乎過程 - React 18 強制 useEffect 兩次 - VD、React elements 也是一樣,不在乎新 render 跟原本的差異,只要知道更新後 UI 應該長什麼樣 ##### 6-2 dependencies :::warning **useEffect 的 dependencies 是一種效能最佳化(而非對邏輯、生命週期的控制??)** ::: ```javascript= // 如果 name 基本上不會變,甚至是一個常數 NAME // 我們經常會認為那就只需要 render once // 想用空陣列來模擬 componentDidMount useEffect(() => { document.title = 'Hello, ' + name; }, []); // 但正確的方式是,我們應該將其加在依賴 // 我們該關注的不是 name 不會改動,所以不需要考量 rerender // 而是 正因為 name 不會改動,我們也得將其加在依賴來確保避免不要的 rerender useEffect(() => { document.title = 'Hello, ' + name; }, [name]); // practice : project example useEffect(() => { if (type === Types.SHOP_MANAGER && user?.store) { updateStoreLink(); } }, []); useEffect(() => { TPDirect.setupSDK( ... ); }, []); ``` * 拿掉「這些程式應該在哪幾次 render 被執行」的想法、也就是不要把「mount、update 作為重點」 * 思考:即使每一次 render 時都執行這個 effect,其邏輯依然能正常運作 * 空依賴項是出現是因為真的沒有依賴項,而不是我只想讓他發生一次 ##### 6-3 error cases * **6-3-1 Race condition**:當 effect 的邏輯包含非同步,則 effect 執行順序不一定會與非同步的回應順序相同 ```javascript= // 不是很好的舉例 function Example() { const [value, setValue] = useState(''); useEffect(() => { const fetchData = async () => { const {data} = await fetch('...'); setValue(data.title); }; fetchData(); }, []); useEffect(() => { const timeoutId = setTimeout(() => { console.log(value); }, 1000); return () => { clearTimeout(timeoutId); }; }, [value]); return <div>{value}</div>; } export default Example; ``` ~~使用 useRef 修正(有問題)~~ ```javascript= function Example() { const [value, setValue] = useState(''); const latestValue = useRef(value); useEffect(() => { const fetchData = async () => { const {data} = await fetch('...'); setValue(data.title); }; fetchData(); }, []); useEffect(() => { latestValue.current = value; }, [value]); useEffect(() => { const timeoutId = setTimeout(() => { console.log(latestValue.current); }, 1000); return () => { clearTimeout(timeoutId); }; }, []); return <div>{value}</div>; } export default Example; ``` **use flag** * **6-3-2 Memory leak**:effect 帶有監聽事件,卻沒有 clean up,導致 component unmount 後繼續監聽 ```javascript= useEffect(() => { const handleClick = () => { setCount(count + 1); }; document.addEventListener('click', handleClick); return () => { document.removeEventListener('click', handleClick); }; }, []); ``` * **6-3-3 user trigger events should not be effect**: 這種情況的真正問題是我們不應該把某些對應「使用者行為意志」的動作放在 effect 令其隨著 render 而被自動多次執行 ```javascript= function Cart() { const [isBuying, setIsBuying] = useState(falase) useEffect( () => { if(isBuying){ // ❌ 這個 request 會在 React 18 的 strict mode + dev env 自動被送出兩次 // 它應該被寫在使用者觸發的事件中,而不是隨著 render 自動執行的 effect 中 fetch('/api/buy', { method: 'POST' }); } }, [isBuying] ); return( <button onClick={()=>setIsBuying(!isBuying)}></button> ) } // better const handleClick = () => { fetch('/api/buy', { method: 'POST' }); } ``` #### 7. useCallback ##### 7-1 design concepts :::warning **useCallback 的本質並不是效能優化,而是「感知資料流是否有變化」、「讓函式參與資料流」** ::: ```javascript= function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = () => { return 'https://hn.algolia.com/api/v1/search?query=' + query;} useEffect(() => { const url = getFetchUrl(); // ... }, [getFetchUrl]); // ... } ``` 根據第六點的說明,我們已經理解必須把 getFetchUrl 也作為依賴,但問題在於 JS 機制對於每次產生的 function 都是全新的 object,等於是依賴項完全沒作用,所以才需要使用 useCallback ```javascript= // add useCallback function SearchResults() { const [query, setQuery] = useState('react'); const getFetchUrl = useCallback(()=>{ async(qeury) => { return 'https://hn.algolia.com/api/v1/search?query=' + query;} },[query]) useEffect(() => { const url = getFetchUrl(); // ... }, [getFetchUrl]); // ... } ``` 從上面的例子來看 當 SearchResults 重新渲染,useEffect 依然會執行 -> 發現依賴 [getFetchUrl] -> 執行 getFetchUrl() 確認有無改變 -> 發現 useCallback 的依賴 [query] 沒變 -> async 不會執行 -> 直接返回原本的結果給 useEffect 裡的 getFetchUrl() ##### 7-2 Optimization :::warning **當 function 作為 useEffect 的 deps 且有妥善的 useCallback 包裝,確實能協助 useEffect 使用 deps 來達到效能優化** ::: 雖然 useCallback 的本質不是為了效能優化,而是為了在適當的時候重新渲染函數元件,但是在某些情況下,使用 useCallback 可以有效地優化效能。當函數作為 useEffect 的依賴項時,使用 useCallback 可以避免在每次重新渲染時重新定義函數,從而減少不必要的渲染,提高效能。