簡單介紹優化 react 的另一個方法 - useCallback
在 react 裡面除了 object 跟 array 以外 function 也是一個非常被拿來當做 props 傳遞的東西,所以也會有重複 create 導致 memo 不起作用的狀況,當然這個情況也可以使用上一篇介紹的 useMemo 來處理。
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
在 function 裡面又再 return 一個 function,有點難以閱讀跟馬上理解,這個時候可以使用 useCallback 來處理。
const cachedFn = useCallback(fn, dependencies)
fn
: 一個希望 react 幫我們記住的 function,可以接收任何參數,也可以回傳任何 value,react 會回傳你所放入的這個 function,但不會執行,讓你在想要使用的地方呼叫這個 function。
dependencies
: 一個 array 裡面應該放著在 fn
裡面有使用到的外部變數,跟其他的 dependencies
一樣 react 會在 re-render 的時候透過 Object.is()
一個個進行比較,當結果有 false 的時候便會給你新建立的 function。
cachedFn
: 初次 render 的時候會跟你放入的 fn
相同,在 re-render 的時候 react 會透過檢查 dependencies
來確認是否有需要給你新的 function。
簡單用個範例來觀察一下 useCallback 是怎麼運作的。
import { useState, memo } from "react";
type Props = {
name: string;
value: number;
onClick: () => void;
};
const Item = memo(function Item({ name, value, onClick }: Props) {
console.log(`Item ${name} render`);
return (
<div>
<p>
{name}: {value}
</p>
<button onClick={onClick}>add {name}</button>
</div>
);
});
function App() {
const [count, setCount] = useState<number>(0);
const [number, setNumber] = useState<number>(0);
const handleCount = () => {
console.log(`handleCount --> number:${number}, count: ${count}`);
setCount(count + 1); // 每次點擊 count +1
};
const handleNumber = () => {
console.log(`handleNumber --> number: ${number}, count: ${count}`);
setNumber(number + 1);
};
return (
<div>
<Item name="Count" value={count} onClick={handleCount} />
<Item name="Number" value={number} onClick={handleNumber} />
</div>
);
}
用 count 跟 number 兩個 state 來觀察 re-render 時候 function 被建立時的狀態。
可以看出不管觸發哪一個 function 出現的 value 都會是 re-render 之前最新的狀態,代表每一次 state 改變都有重新宣告新的 function。
而且我們的 memo 沒有起作用每一次 state 改變 Item
還是進行了 re-render 了。
接著就來使用 useCallback 吧。
import { useCallback, useState, memo } from "react";
type Props = {
name: string;
value: number;
onClick: () => void;
};
const Item = memo(function Item({ name, value, onClick }: Props) {
console.log(`Item ${name} render`);
return (
<div>
<p>
{name}: {value}
</p>
<button onClick={onClick}>add {name}</button>
</div>
);
});
function App() {
const [count, setCount] = useState<number>(0);
const [number, setNumber] = useState<number>(0);
const handleCount = useCallback(() => {
console.log(`handleCount --> number:${number}, count: ${count}`);
setCount(count + 1); // 每次點擊 count +1
}, [count]);
const handleNumber = useCallback(() => {
console.log(`handleNumber --> number: ${number}, count: ${count}`);
setNumber(number + 1);
}, [number]);
return (
<div>
<Item name="Count" value={count} onClick={handleCount} />
<Item name="Number" value={number} onClick={handleNumber} />
</div>
);
}
這邊兩個 useCallback 應該都有亮起來,會請我們把使用到的變數放到 dependencies
裡,因為在 console.log()
裡面兩個都有使用到。
可以看到第一次執行 handleNumber 的時候 console 出來的 count 是 0,代表我們執行的是最初畫面初始化時候的 handleNumber,而且我們的 memo 也起作用了當 props 沒有改變的元件不會出現多餘的 re-render。
通常會在兩個情況下使用 useCallback
dependencies
所以為了減少其他 react hook 的執行次數。useCallback 並不會因為 dependencies
一樣就不產生新的 function,react 還是會產生新的 function 但是 react 會依照 dependencies
的比較結果,決定要給你 cache 的 function 還是新產生的 function。
另外 useMemo 跟 useCallback 因為語法上很接近,所以容易搞混。
兩者的差別就是 useMemo 會執行傳入的 function 並且可以回傳任何的 value,useCallback 不會執行傳入 function,而是回傳被傳入的function。
下一篇簡單介紹 lazy 跟 Suspense 的用途跟用法
如果內容有誤再麻煩大家指教,我會盡快修改。
這個系列的文章會同步更新在我個人的 Medium,歡迎大家來看看 👋👋👋
Medium