初學的 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