---
# System prepended metadata

title: React Hook 筆記
tags: [web dev]

---

---
tags: web dev
---
# React Hook 筆記

:::spoiler 目錄
[TOC]
:::


## 被計算出來的狀態使用 `useMemo`

### 範例：計算按鈕何時應啟用 disabled 樣式

```javascript=
// Before: 自己寫 useEffect 去監聽依賴，在依賴變動時重新賦值
const [input, setInput] = useState('')
const [isInputError, setIsInputError] = useState(false)
const [isBtnDisabled, setIsBtnDisabled] = useState(true)

useEffect(() => {
  setIsBtnDisabled(input === '' || isInputError)
}, [input, isInputError])

// ------------------------------------------------------
// After: 使用 useMemo

const [input, setInput] = useState('')
const [isInputError, setIsInputError] = useState(false)
const isBtnDisabled = useMemo(() => {
  return (input === '' || isInputError)
}, [input, isInputError])
```

`useMemo` 也可以 memorize 一個 functional component

### 備註

同樣是監聽一些 state 去響應另一個 state，`useMemo` 的改寫我覺得可讀性較高。

在 Vue.js 中，`computed` 屬性在做類似的事情，跟 React 不同的是，Vue.js 會自動追蹤計算的過程牽扯到哪些依賴，也就是不需要 dependency array。

老實說，我翻了一些的 realworld example，卻不常看到 `useMemo` 被使用，還蠻令我驚訝的，我想 `useMemo` 應該不僅僅是這樣，有空會再尋找它在實際上是如何被使用的。

參考資料：
- [Hooks API 參考](https://zh-hant.reactjs.org/docs/hooks-reference.html#usememo)
- [Vue computed](https://v3.vuejs.org/guide/computed.html#basic-example)

---

## Child Component 使用 React.memo

在 functional component 之前，React 提供 `React.Component` 和 `React.PureComponent` 來撰寫元件。

[React.Component - React](https://zh-hant.reactjs.org/docs/react-component.html)

其中的差異在於，`React.Component` 需自行實作 `shouldComponentUpdate` 這個 function 來控制在 props 或 states 更新後是否需要重新 render，而 `React.PureComponent` 則沒有 `shouldComponentUpdate`，會以 shallow compare 的方式來比較舊的 props, states 和新的 props, states 來決定是否重新 render。

在 functional component 中，當 parent component 觸發了重新渲染後，child component 也會重新 render，**即使 props 沒有更新**，若要避免這個問題，應該使用 `React.memo` 來包住 child component，他的用途是對於 props 做 shallow compare 來決定是否需要重新 render。

另外，`React.memo` 可以在第二個參數傳遞一個自定義的 compare function，也就是保留了彈性，當不想要用其預設的 shallow compare，可以自已撰寫邏輯。

### 範例：使用 `React.memo` 包裹子元件
打開 console 可以發現，一開始初始化 App 後三個 component 都被渲染出來，之後在按下 add count1 按鈕時，`Child1` 跟 `Child2` 更新了，其中我們知道 `Child1` 有關注 `count1` 所以更新的合理，但 `Child2` 在這個情況下可以不必更新。
而用 `React.memo` 包起來的 `MemoChild2` 不會在每次父元件更新就跟著更新，而是做 shallow compare。

<iframe src="https://codesandbox.io/embed/admiring-forest-7mhki?fontsize=14&hidenavigation=1&theme=dark"
     style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
     title="admiring-forest-7mhki"
     allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
     sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
   ></iframe>

### 使用時機
沒有使用 `React.memo` 可能會導致子元件做不必要的更新，但使用了 `React.memo` 也有 shallow compare 帶來的成本。
> 問題：shallow compare 的成本跟重新渲染的成本相比是不是小很多？

假如父元件將更新 $N$ 次，而其中 $M$ 次 props 並沒有更新，另外 $N-M$ 次 props 有更新，那麼在 $M$ 較小時我們應該不使用 `React.memo`，應該在 $M$ 較大時使用。

這樣來看，在 Container component 與 Presentational component 之間就不需要用 `React.memo` 包住 Presentational component，因為幾乎每次更新都是因為 props 更新。

想一個相反的情境，例如表單 component 有較多狀態的情況，若有包含其他 component 如錯誤時要跳出來的 Alert，因為他只關心少數狀態，就可以使用 `React.memo`，或者用 `useMemo`。

### 備註
在 Vue.js 中，子元件的重渲染機制預設就使用 shallow compare，目前看來在 React 中要自己 handle。

參考資料：
- [React 頂層 API](https://zh-hant.reactjs.org/docs/react-api.html)
- [我該如何實作 shouldComponentUpdate？](https://zh-hant.reactjs.org/docs/hooks-faq.html#how-do-i-implement-shouldcomponentupdate)
- [關於 props 的記憶，React Memo](https://ithelp.ithome.com.tw/articles/10240296)

