## React Hook 介紹
:::info
:bulb: **React Hook 可以讓 function component (函式元件) 也具備像 class component 生命週期的能力。**
:bulb: **React Hook 可以根據程式邏輯拆分成多次呼叫。** 即不會受到生命週期或拆分程式邏輯影響。
:::
### Hook 使用規則
1. 只能在 React Function Component 中呼叫 Hook,Class Component 是不能用 Hook。
2. 只能於組件的 **最頂層呼叫**:不要在 **迴圈、條件式或是巢狀的 function 內** 呼叫 Hook,這樣才能確保程式中每一次 component render 時 Hook 被呼叫順序都要是一樣的。
### useState 鉤子
範例程式碼:
```javascript
const [state, setState] = useState(initialState);
// 以箭頭函式執行初始化,只會執行一次
const [state, setState] = useState(() => {
// code ...
return initialState;
});
```
#### 使用說明
1. useState 會回傳當前的 state 及一個用來更新 state 的函式(setState)。
2. 首次渲染會用 initialState(初始值),後續則使用最近設定的 state 值渲染。
3. setState 為 **「批次更新」**,會在下次 batch re-render 一併更新元件狀態。
- Batching of state updates:(批次更新 => 改善效能)
3.1. React 不會在每次 setState 後就馬上同步地執行 re-render,而是將多個 setState 累積加入到 queue 中,
3.2. 於一次 re-render 階段更新多個 state,藉由批次處理 state updates 就只要 re-render 一次來改善效能。
4. 當 state 改變,便會觸發 re-render。
### useEffect 鉤子
範例程式碼:
```javascript!
const App: React.FC<IApp> = (props) => {
useEffect(() => {
// do something ...
return () => {
// cleanup function
};
}, [dependencies]);
}
```
#### 使用說明
1. 若**無**設定第二個參數(即**無逗號**),則於組件每次渲染完後被呼叫執行。
2. 若**有**設定第二個參數,只要每次重新渲染後 dependencies 陣列內的元素沒有改變,則任何 useEffect 裡面的函式就不會被執行。
3. 若第二個參數為空陣列,則組件重新渲染後 <font color="red">**執行 1 次**</font> 後,**就不會再執行**。
4. 回傳一個函式即清除函式,用於 **註銷或解除** 元件銷毀後將不再使用的事件或資源。
5. 可實現 Class Component 中 componentDidMount()、componentDidUpdate() 與 componentWillUnmount() 生命週期函式。
#### 使用場景
當 React 更新完 DOM 後,需要執行其他程式(side effect),如網路請求、操作 DOM 等,就可以使用 useEffect 鉤子。
:::success
:bulb: cleanup 函式的執行時機點
- **update 階段:**
1. (React 16) <font color='blue'>**執行 cleanup 函數**</font> → 畫面更新(re-render) → **執行 effect**
2. (React 17) 畫面更新(re-render) → <font color='blue'>**執行 cleanup 函數**</font> → **執行 effect**
- **unmount 階段:**
1. (React 16) 元件更新 → <font color='blue'>**執行 cleanup 函數**</font> → DOM 作對應改變 → 畫面更新(re-render)
2. (React 17) 元件更新 → DOM 作對應改變 → 畫面更新(re-render) → <font color='blue'>**執行 cleanup 函數**</font>
:warning: React 17 於各階段 cleanup 函式皆改到 **畫面更新之後** 才執行。
:::
### useCallback 鉤子
範例程式碼:
```javascript!
const App: React.FC<IApp> = (props) => {
const fn = useCallback(() => {
// do something ...
}, [dependencies]);
}
```
#### 使用說明
1. 第一個參數為 **箭頭函式**,可將函式邏輯放於此處。
2. 第二個參數為 **此函式感知變動的相依陣列**:相依元素變動,則函式實體 **重新生成**。
3. useCallback 回傳值可作為其他 useEffect 的相依元素。
#### 使用場景
暫存函式實體,若渲染時 **相依陣列內的元素發生改變**,才會生成並回傳新的函式實體,否則就維持舊的函式實體(不重新生成),可作為 **效能最佳化** 的手法之一。
:::success
每次 render 時,元件內的函式實體都會重新產生,useCallback 可以改善此函式實體可藉由 **感知資料流變化** 才發生改變。
:::
:::danger
**誤區說明**
:warning: 通常與 **shouldComponentUpdate/React.memo** 搭配使用,才可真正節省效能:透過 React.memo 包裝的元件可節省在 **相依參數不變** 的狀況下因傳入的函式實體一直變更而產生 **重複渲染** 效能。
一般元件應該不需要使用。
:::
### useRef 鉤子
#### React 官方定義
><font color='blue'>useRef</font> is a React Hook that lets you reference a value **that’s not needed for rendering.**
:::success
:bulb: 回傳一個 Ref 物件(可為值、DOM 元素或計算結果),當 Ref 若發生變更,則 **不會觸發** 畫面更新(re-render)。
:::
#### React 使用場景
1. **Referencing a value with a ref(使用 ref 對照值)**
可以使用 useRef <font color='orange'>建立一個可變的值</font>,且數值變更後不會引發重新渲染。
3. **Manipulating the DOM with a ref(使用 ref 操作 DOM )**
可以使用 useRef 來直接操作 DOM 元素。通過將 ref 對象的 .current 屬性<font color='orange'>設置為一個 DOM 節點</font>,可以直接讀取或修改這個節點。
5. **Avoiding recreating the ref contents(避免不必要的重建 ref 內容)**
使用 useRef 可以幫助你避免在重新渲染 component 時不必要地重建 ref 的內容,<font color='orange'>因為 ref 會在 re-render 中保持不變</font>,例如一些資源很大的影音 component 就可以使用此方法,避免重複拿取資源、更新畫面。
### React.memo 高階元件 (HOC)
範例程式碼:
```javascript!
// memoSample.ts
import React from 'react';
import { ISample } from './types';
const Sample: React.FC<ISample> = (props) => {
return (
<>
<div>Hi, {props.name}</div>
</>
);
}
const MemoSample = React.memo(Sample);
export default MemoSample;
```
#### 使用說明
1. 包入 React.memo 的 React 元件會 **判斷傳入 props 是否發生改變**,而決定是否觸發畫面更新(re-render),可作為 **效能最佳化** 的手法之一。
2. 若是傳入的 props 中有 **函式** 參數,若此函式實體於每次渲染都不同的話,則不會有效能最佳化的效果(每次都會重新渲染),但可用 useCallback 解決此問題。
```javascript!
// memoSample.ts
import React from 'react';
import { ISample } from './types';
const Sample: React.FC<ISample> = (props) => {
return (
<>
<div>Hi, {props.name}</div>
<button onClick={props.showAlert}>alert!</button>
</>
);
}
const MemoSample = React.memo(Sample);
export default MemoSample;
```
```javascript!
// app.ts
import React from 'react';
import MemoSample from './memoSample';
const App: React.FC = (props) => {
// 用 useCallback 包住可以起到效能最佳化的效果!
const showAlert = useCallback(() => alert('hi'), []);
return (
<MemoSample
name="Sam"
showAlert={showAlert}
/>
);
}
export default App;
```
### useMemo 鉤子
用法與 useCallback 相似,可用來記憶 **陣列或物件類型** 的資料集合,或是 **複雜的計算結果**。
```javascript!
// sample.ts
import React from 'react';
import { ISample } from './types';
const Sample: React.FC<ISample> = (props) => {
return (
<>
<div>Hi, {props.name}</div>
{props.numbers.map(it => (
<h4>{it}</h4>
))}
</>
);
}
const MemoSample = React.memo(Sample);
export default MemoSample;
```
```javascript!
// app.ts => 無效能最佳化
import React from 'react';
import MemoSample from './sample';
const App: React.FC = () => {
// 每次畫面更新必定為新的參考!
const numbers = [1, 2, 3];
// effect 的 deps 效能最佳化永遠都會失敗
useEffect(() => {
console.log(numbers);
}, [numbers]);
// <MemoSample> 的 render 快取效能最佳化永遠都會失敗
return (
<MemoSample
name="Sam"
numbers={numbers}
/>
);
}
export default App;
```
```javascript!
// app.ts => 有效能最佳化
import React from 'react';
import MemoSample from './sample';
const App: React.FC = () => {
// 用 useMemo 包住陣列
const numbers = useMemo(
() => [1, 2, 3],
[]
);
// effect 的 deps 效能最佳化可以正常運行
useEffect(() => {
console.log(numbers);
}, [numbers]);
// <MemoSample> 的 render 快取效能最佳化可以正常運行
return (
<MemoSample
name="Sam"
numbers={numbers}
/>
);
}
export default App;
```