[React Hooks] useCallback 學(xué)習(xí)

官方文檔: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;
  • 初始化加載后
    如圖,初始化加載后numnum1顯示分別為 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ù)。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容