今天介紹的是 useCallback hook,透過它可以對一個函式重新執行的時機做出控制。
useCallback 真的是效能優化的利器?倒也不一定,看你怎麼使用它,過度使用反而只是拖垮效能罷了
useCallback 的用法是將一個函式包覆並將該函式記憶起來,最後回傳記憶的函式。
useCallback: 記憶的是函式
useMemo: 記憶的是函式執行後的回傳值
透過 useCallback 的記憶功能去避免子元件不必要的渲染,本文結尾的部分有範例實作這點。
在 React 官方文件提到 在 render 時建立 function,Hooks 會變慢嗎?,看過內容後就知道不會,因此 useCallback 不是為了解決元件內部過多內部函式導致的性能問題。
除了可以減少不必要的渲染外,useCallback 也可以彌補 React.memo 的缺點,還記得 React.memo 是 shallow compare 嗎?
透過 useCallback 可以記住 function 的記憶體位置,就可以避免 React.memo 在比較 props 值時因為"物件型別記憶體位置不同但值相同"而重新渲染的狀況。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback(callback)
: 如果沒有加上這個陣列,每次都會重新執行函式去產生新的函式useCallback(callback, [])
: 空陣列的話,回傳的函式不會改變useCallback(callback, [...someValues])
: 有加上一些元素值的話,當元素值改變時會重新更新回傳的函式在 resetCount 函式加上 useCallback,使得 Child 元件不會重新渲染。
App.js
import React, { useState, useCallback } from "react";
import Child from "./Child";
const App = () => {
const [count, setCount] = useState(0);
console.log("re-render parent component");
const resetCount = useCallback(() => {
setCount(0);
}, []);
return (
<>
<p>Count: {count}</p>
<button onClick={() => setCount((count) => count + 1)}>Increment</button>
<Child reset={resetCount} />
</>
);
};
export default App;
Child.js
import React, { memo } from "react";
const Child = memo(({ reset }) => {
console.log("re-render child component.");
return (
<>
<p>child component which resets count</p>
<button onClick={reset}>Reset Count</button>
</>
);
});
export default Child;