# 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 可以避免在每次重新渲染時重新定義函數,從而減少不必要的渲染,提高效能。