官方文檔:https://reactjs.org/docs/hooks-reference.html#usecallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback 接收一個(gè)內(nèi)聯(lián)回調(diào)函數(shù)和一個(gè)依賴數(shù)組,返回一個(gè)記憶版本的回調(diào)函數(shù),只有當(dāng)依賴發(fā)生變化的時(shí)候,回調(diào)函數(shù)才會(huì)改變。這在將回調(diào)傳遞給優(yōu)化的子組件時(shí)非常有用,這些組件依賴引用相等性來(lái)防止不必要的渲染(引用沒(méi)有發(fā)生變化)
所以我們這里將帶著一個(gè)問(wèn)題閱讀下面的內(nèi)容:當(dāng)依賴發(fā)生改變的時(shí)候,變量
memoizedCallback的引用會(huì)發(fā)生變化嗎?
下面給個(gè)代碼例子:
import React, { useState, useCallback } from 'react';
const set = new Set();
function App() {
const [num, setNum] = useState([1,2,3]);
const [num1, setNum1] = useState([4,5,6]);
const mCallback = useCallback(() => { console.log('callback function');return [...num, ...num1] }, []);
return (
<>
<ChildrenApp num={num} num1={num1} callback={mCallback} />
<button onClick={() => setNum([7,8,9])} >change num</button>
<button onClick={() => setNum1([10,11,12])} >change num1</button>
</>
)
}
function ChildrenApp({num, num1, callback}) {
set.add(callback);
console.log(set);
return (
<div>
{num} - {num1} - {callback()}
</div>
)
}
export default App;
- 初始化加載后
如圖,初始化加載后num和num1顯示分別為 123 和 456,callback 函數(shù)顯示結(jié)果為 123456。set 集合里面只有一個(gè) callback 函數(shù),callback 函數(shù)被調(diào)用,打印信息 callback function
-
點(diǎn)擊 change num,num 變?yōu)?789,頁(yè)面以及console 顯示如下圖:
-
點(diǎn)擊 change num1 后,如下圖:
從上面的過(guò)程我們可以發(fā)現(xiàn),set 集合里面一直只有一個(gè)函數(shù),這說(shuō)明了 callback 函數(shù)的引用沒(méi)有發(fā)生變化,使用的是記憶化的版本函數(shù)。這里之所以 callback 顯示的值一直是 123456 沒(méi)有變化,原因在于 mCallback 函數(shù)的依賴列表為空,并沒(méi)有監(jiān)聽 num 和 num1 的變化,所以其閉包里面的 num 和 num1 值一直分別是 123 和 456。
下面我們做點(diǎn)變化,修改下 mCallback 函數(shù)內(nèi)容如下:
const mCallback = () => [...num, ...num1];
-
初始化加載
-
點(diǎn)擊 change num
-
點(diǎn)擊 change num1
整個(gè)流程下來(lái),我們可以看到 set 里面包含了三個(gè) function,說(shuō)明了每次重新渲染的時(shí)候都會(huì)重新生成 mCallback 函數(shù)并傳遞給子組件。
下面我們?cè)傩薷男薷?mCallback 函數(shù),給它添加上依賴列表,代碼如下:
const mCallback = useCallback(() => { console.log('callback function');return num }, [num]);
-
初始化過(guò)程
-
點(diǎn)擊 change num
這次我們?cè)谝蕾嚵斜砝锩姹O(jiān)聽了 num ,所以當(dāng) num 發(fā)生變化的時(shí)候,mCallback 函數(shù)會(huì)重新生成,所以 set 里面便有了兩個(gè)函數(shù)。
-
點(diǎn)擊 change num1
當(dāng)我們點(diǎn)擊 change num1 的時(shí)候,set 里面依然是兩個(gè)函數(shù)。因?yàn)槲覀冊(cè)谝蕾嚵斜砝锩鏇](méi)有監(jiān)聽 num1,所以即使 num1 發(fā)生了變化,mCallback函數(shù)也不會(huì)重新生成,而是繼續(xù)使用記憶化的歷史版本。
總結(jié):
- 普通函數(shù)在重新渲染時(shí)會(huì)重新生成,所以引用會(huì)變。
- 使用 useCallback 包裝的函數(shù),依賴列表為空時(shí),重新渲染時(shí)該函數(shù)會(huì)使用記憶化的版本函數(shù),所以引用不會(huì)變化
- 使用 useCallback 包裝的函數(shù),依賴列表存在依賴,重新渲染時(shí)對(duì)應(yīng)的依賴發(fā)生變化,該函數(shù)會(huì)重新生成,所以引用發(fā)生變化,
這里就完了嗎?不,還有一個(gè)問(wèn)題。為什么依賴發(fā)生變化的時(shí)候需要重新生成函數(shù)那?這里面和作用域的知識(shí)有點(diǎn)相關(guān)。
- 首先我們看看普通函數(shù)
ChildrenApp 的 callback 函數(shù)其實(shí)是指向 App 的 mCallback 函數(shù)的,即 callback -> mCallback,所以調(diào)用 callback 函數(shù)也就是調(diào)用 mCallback 函數(shù),里面用到的 num 和 num1 是 App 組件作用域里面的 num 和 num1。 - 再來(lái)看看依賴為空的情況
和上面一樣,ChildrenApp 的 callback 函數(shù)其實(shí)是指向 App 的 mCallback 函數(shù)的,即 callback -> mCallback,但是我們這里的 mCallback 函數(shù)是一個(gè)記憶化版本函數(shù),在初始化的時(shí)候,App 作用域下的 num 和 num1 傳給 mCallback 函數(shù)閉包環(huán)境里面的 num 和 num1。因?yàn)槲覀兊囊蕾嚵斜頌榭?,不監(jiān)聽變化,所以閉包里面的內(nèi)容不會(huì)發(fā)生變化。所以 mCallback 函數(shù)也就沒(méi)有重新生成。 - 最后看看有依賴的情況
和依賴為空的差別在于,例如當(dāng)依賴 num 發(fā)生變化的時(shí)候,mCallback 閉包環(huán)境里對(duì)應(yīng)的 num 會(huì)被重新賦值,這個(gè)流程會(huì)觸發(fā)生成一個(gè)新的函數(shù)。








