---
# System prepended metadata

title: React misunderstanding issues (Part 2)

---

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

