⚓ useState
- useState
- updater
什麼是 useState
在元件裡面透過 useState 讓每個元件保有自己獨立的 State(狀態)。
何時使用 useState
- 該 state 只需要在該元件中使用
- 狀態改變時,元件需要重新繪製
使用 updater 取得最新的 state
setState 是非同步執行的,若要馬上取得最新的 state,需使用 updater。
其實 updater 就只是 setState 的 Callback 函式而已,預設有兩個引數,分別是最新的 state 值與最新的 props 值。
處理物件或陣列的注意事項
處理物件或陣列時,要先產生一個全新的物件或陣列再更新 State,因為它們是參考到記憶體位置。
陣列可以用展開運算子或 Array.prototype.map;物件可以用展開運算子或 Object.assign 處理。
⚓ useEffect
- useEffect
- 生命週期
- componentDidMount / componentDidUpdate / componentWillUnmount
生命週期
- 元件渲染完 = componentDidMount(就是 Vue 的 mounted)
- 元件 state 改變前/後 = componentDidUpdate(Vue 的 watch 有這些功能)
- 元件被移除時 = componentWillUnmount(感覺類似 Vue 的 beforeDestroyed)
依賴(第二個參數)
useEffect 第二個參數可決定是否要觸發函式,接收一個陣列,也被稱作該作用的依賴。
- 如果完全「沒寫」依賴,那這個函式會在每一次渲染時都觸發
- 如果依賴只放一個「空陣列」,那就代表函式只在渲染後觸發第一次
- 如果「有寫」依賴,就只有當依賴中的 state 改變,並造成畫面渲染時,才會觸發函式
依賴的寫法會影響如何執行
1. 在每次元件渲染時執行
如果 Dependency 直接「省略」不寫(沒有 Dependency)的話,表示沒有要對比的東西。這樣會導致每個元件渲染時,都會執行函式。
2. 只在元件渲染後執行一次
Dependency 放「空陣列」的話,就只會執行一次函式。
使用情境:獲取資料、添加事件監聽。
3. 在依賴的狀態改變時執行
當 Dependency 陣列裡面的狀態改變,導致畫面重新渲染後,就會觸發 useEffect 的函式。
4. 在依賴的狀態改變、元件被移除之前執行
與上一個情形類似,也是在 state 改變的時候會執行,不過這裡會以「改變前的 state 值」來觸發執行。
要捕捉到 state 改變前的值,我們要在 useEffect 的函式中回傳 (return) 一個函式,這個函式會比上面那個原本的函式還要先被觸發。
使用情境:停止獲取資料、清除監聽。
可以看以下的範例幫助理解:
useEffect(() => {
1;
return () => {
2; // 使用 state 改變前的值
};
});
- 元件被渲染完成時,執行 (1)
- 元件被移除前,執行 (2)
- 元件內的 state 改變,導致元件重新渲染的話,先執行 (2) 再執行 (1)
⚓ React.memo 與 useMemo
- React.memo
- useMemo
使用情境
當元件的 state 改變時,除了該元件重新渲染之外,其實整個 DOM 全部都會被重新渲染!
因為有些不需要改變的地方也被重新渲染了,所以這時候就會被稱為「不必要的渲染」。
改善方法 (1) React.memo
使用 React.memo 把不要被渲染的元素包起來,就能夠避免不必要的重新繪製了。
原理
React.memo 的原理是以 props 有無改變,來判斷是否需要重新渲染。
因此如果根本沒有 props,就代表不會有變化的時候,也就表示不會被重新渲染了!
const Title = React.memo(() => {
return {
<div>
<h1>不會被重新渲染</h1>
</div>
}
});
傳入參數
React.memo 的第一個參數是回傳一個元件。
第二個參數則是可以傳入一個函式,它有兩個引數,分別是舊的 props 與新的 props。最後回傳時如果回傳 false 就會渲染(表示新舊 props 不同),回傳 true 則不會渲染。
改善方法 (2) useMemo
使用情境與解決的問題都和 React.memo 一樣,但這是 Hooks 版本的寫法。
const Title = useMemo(()=> {
return {
<div>
<h1>不會被重新渲染{title}</h1>
</div>
}
}, [title]);
參數
第一個參數是函式。
第二個參數是依賴的 state,這與 useEffect 相同:
- 只有當依賴被改變時,才會重新執行函式並回傳新的結果
- 沒寫第二個參數 = 任何 state 改變都會重新執行函式
- 只寫空陣列 = 只會執行第一次,永遠不會重新執行
⚓ useCallback
- useCallback + updater
- 「效能優化」是在效能出現問題時才處理,不用對還沒出現問題的元件超前部署一堆多餘的配置
目的 & 使用情境
跟 useMemo 目的一樣,都是要避免不必要的渲染,不過 useCallback 可以處理一些特殊情況。
例如:元件重新渲染時,因為 React.memo 與 useMemo 都是做「淺層比較」,所以就算出現內容完全一樣的函式,但由於是不同的物件實體,所以它們會認為兩者是不同的,便會讓元件重新渲染。
大概原理
useMemo 就像是記憶住函式執行的「結果」,而 useCallback 則是記憶住「整個函式」的宣告。
搭配 setState 的 updater
使用 useCallback 時,如果函式裡面有 setState 可能會出現更新不了的問題?!
這是因為 React 的 state 更新是 immutable,所以每次更新都需要一個新的記憶體位置,然而 useCallback 卻是用一個記憶體位置記住函式的宣告,所以會無法更新。
const increment = useCallback(() => {
setCount(count + 1); // count 永遠都是最初始的 0
});
上方程式碼中,就算怎麼 setState,在 useCallback 裡面的 state 都還是最初的那個記憶體位置,而不是更新後的記憶體位置。
解決方法就是使用之前提到的 updater,來強迫 setState 使用最新的值!
const increment = useCallback(() => {
setCount((newCount) => newCount + 1); // 使用 count 最新的值
});
⚓ Custom Hooks
我們也可以創造自己的 Hooks,把可以共用邏輯的函式抽出去,製作成 useXXX 的自定義 Hooks,讓不同元件可以共享相同的一套邏輯。
舉例來說,一般的計數器點一次會加一,但今天有個超級計數器點一次要加 10,此時就可以把計數的邏輯拆分出去。
// 新建一個自定義 Hooks 名為 useCounter.js
import { useState, useEffect } from 'react';
const useCounter = (initialCount, callbackFunction) => {
// 使用外部提供的初始值
const [count, setCount] = useState(initialCount);
// 外部使用 add 函式時可以決定一次加多少
const add = (added) => {
setCount(count + added);
};
// 加上 count 變更時的作用
useEffect(callbackFunction, [count]);
// 用物件格式 {count, add} 也可以
return [count, add];
};
export default useCounter;
我們給 useCounter 以下功能:
- 參數 1 (initialCount):從外部接收 count 的初始值
- 參數 2 (callbackFunction):從外部接收一個函式,當 count 改變時會執行
- 提供外部 count 的值
- 提供外部一個函式 (add),用來處理 count 的計算邏輯,一次加多少 (added) 由外部使用時決定
最後回傳的格式用陣列或物件都可以,解構時記得使用對應的格式即可。
// 使用 useCounter
import useCounter from '../../hooks/useCounter';
const SuperCounter = () => {
// 把初始值與函式傳給 useCounter,並解構取出回傳的 count 與 add
const [count, add] = useCounter(100, () => console.log('超級計數器執行了'));
// 使用 useCounter
return (
<>
<div>目前的超級數字:{count}</div>
<button
onClick={() => {
add(10); // 點一次加 10
}}
>
點我可以一次加 10
</button>
</>
);
};
以上資源是我自己整理過後的筆記,若有錯誤歡迎隨時和我聯繫