Finally, I understand what React.useCallback is…

Jayson Chiang
6 min readMar 28, 2022

我一次又一次的看了 useCallback 的官方文件和網路文章,但始終無法有脈絡的深入了解怎麼用。直到看了這篇文章。

必需說,我一定解釋,甚至只是引用及翻譯這篇文章,但終算是用了幾個簡單的例子,得以推論為什麼要這樣用。

先說,其他文章都怎麼描述 useCallback

  1. 回傳一個 memoized callback,意即在 re-render的時仍回傳同一個 function
  2. useCallback(fn, deps) 等於useMemo(() => fn, deps)

這些都是正確的,但對說來說到底怎麼實用,還是需要有一步步的說明與推論。

const aFunc = () => {
return 1 + 2; // 與 bFunc 相同
}
const bFunc = () => {
return 1 + 2; // 與 aFunc 相同
}
// 相同的實作, 不同的物件(不同的實體,不同的參考)
aFunc === bFunc; // false
// 相同的物件! (因為比較的是參考的位置)
aFunc === aFunc; // true

光是看完上例,我就知道為什麼要使用 useCallback 了,在JavaScript中,function也是物件,每一次function的宣告,即便是相同的實作(在上例中,是 return 1 + 2),也是不同的物件,因為他們的「參考」不同。

在 JavaScript 中因為 function 是一級函式,function 不但可以宣告成變數,也可以當變數傳遞。因此也可以比較,但因為 function 是物件,所以比較的是「參考」的位置。(註:實體與參考在本文一體兩面,下文不特別統一名詞)

因為React每次的 re-render都會將所有的物件更新,「參考」的位置也會更新。但對於 function 而言,即使參考更新了,但實作還是相同的呀。因為沒有更新 function 的必要,因此用 useCallback 記住這些 function (的參考)。在 re-render的時侯就會回傳同一個 function (不止是實作,而是真正在同一個參考的 function )

const ParentComponent = () => {
const onHandleClick = useCallback(() => {
// 每一次的 re-render都會回傳同一個實體
});
return (
<MemoSubComponent
handleClick={onHandleClick}
/>
);
}

有了上面的理解後,再來看到底什麼地方才是正確使用 useCallback ,在同一個作者的文章,有非常好的範例

在 React中,大多數的 Component 會在State改變時,一律更新。為了得到最佳的效能,可以使用 React.memo 包住Compoent,僅在 props 改變的時侯,才更新。

const SubComponent = ({ text }) => {
return (
<div>
SubComponent: { text }
</div>
);
}
const MemoSubComponent = React.memo(SubComponent);

這樣可以得到很棒的效能改善。但若 props 是 function 的時侯會發生什麼事

const SubComponent = ({ handleClick }) => {
return (
<div onClick={handleClick}>
SubComponent
</div>
);
}
const MemoSubComponent = React.memo(SubComponent);const ParentComponent = () => {
return (
<MemoSubComponent
handleClick={() => {
// 這個 function 在每一次的render 都會是新的實體
}}
/>
);
}

如上例,如果 ParentComponent 要進行 re-render,因為 re-render就是會給予新的參考,所以 SubComponent 會以為拿到新的 function。於是即便是同一個實作,但SubComponent每次都會 re-render,就失去 React.memo 的義意了。

這時侯的最佳解法,就是使用 useCallback

const ParentComponent = () => {
const onHandleClick = useCallback(() => {
// 每一次的 re-render都會回傳同一個實體
});
return (
<MemoSubComponent
handleClick={onHandleClick}
/>
);
}

這時侯再回來讀 useCallback 的定義,就有義意多了

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary rendershttps://reactjs.org/docs/hooks-reference.html#usecallback

--

--