初學的 useState 其實可以做蠻多事情,但 useState 有個很大的缺點是 每次 state 改變,整個component都會re-render一次,這樣會有什麼問題?為什麼需要學習 useMemo?
useMemo 是一種優化(optimisation)方法,它保證只針對 Dependency 有改變時,才會取得上次render的值去呼叫函數。
用這部影片的範例來說明,假設現在我們有個Counter component,並且做了兩個計數器:
import {useState} from 'react';
export default function Counter () {
const [counterOne, setCounterOne] = useState(0);
const [counterTwo, setCounterTwo] = useState(0);
const isEven = () => {
let i = 0;
while( i < 100000000000 ) ++i; // 每個state改變都會re-render整個component並invoke isEven
return counterOne % 2 === 0;
}
return (
<>
<div>
<button onClick={()=>setCounterOne(counterOne+1)}>Counter1: {counterOne}</button>
<span>{isEven() ? 'Even' : 'Odd'}</span>
</div>
<div>
<button onClick={()=>setCounterTwo(counterTwo+1)}>Counter2: {counterTwo}</button>
</div>
</>
);
}
假設初始值是0,我點擊 Counter1 按鈕,可預期按完後要過一陣子才會顯示 1;但如果只用 useState 的問題就是:即使我只點擊 Counter2 按鈕,同樣會過一陣子才會出現1。
理由在於:只要component的任一個 state 改變,整個 component 都會re-render,所以不論點擊哪顆按鈕,都會呼叫 isEven 這個很expensive的函數。
我們希望只有當我點擊 Counter1 按鈕,才會呼叫 isEven 函數,這個時候能用 useMemo 來幫忙:
import {useState, useMemo} from 'react';
export default function Counter () {
const [counterOne, setCounterOne] = useState(0);
const [counterTwo, setCounterTwo] = useState(0);
const isEven = useMemo(() => {
let i = 0;
while( i < 100000000000 ) ++i;
return counterOne % 2 === 0;
}, [counterOne]); // isEven只在counterOne改變時才被invoke
return (
<>
<div>
<button onClick={()=>setCounterOne(counterOne+1)}>Counter1: {counterOne}</button>
<span>{isEven ? 'Even' : 'Odd'}</span>
</div>
<div>
<button onClick={()=>setCounterTwo(counterTwo+1)}>Counter2: {counterTwo}</button>
</div>
</>
);
}
這個修改會改變兩件事情:
- 點擊
Counter1按鈕才會呼叫isEven,且useMemo會取得上一次re-render後的Counter1值去計數; - 點擊
Counter2按鈕並不會呼叫isEven,所以可以預期點擊後不會過一陣子才看到改變後的數字。
References

![[ 紀錄 ] 實戰練習 - 留言版 (實作前端)](https://static.coderbridge.com/img/stella572322/58ad51fec2b74bc799547b67c48c1758.jpg)
![[JS] 認識變數:let、const、var](https://static.coderbridge.com/images/covers/default-post-cover-3.jpg)