詳解react中useCallback內(nèi)部是如何實(shí)現(xiàn)的
示例demo與debug
新建了一個(gè)react項(xiàng)目,將APP.tsx改寫(xiě)成如下代碼
import { useCallback, useState } from 'react';
function App() {
const [num, updateNum] = useState(0);
const TestCallback = useCallback(() =>{
console.log('num: ', num);
},[]);
return (
<div className="App">
<p onClick={() => {
updateNum(num => num + 1);
updateNum(num => num + 1);
updateNum(num => num + 1);
}}>{num}</p>
<p onClick={TestCallback}>打印</p>
</div>
);
}
export default App;在瀏覽器的source設(shè)置斷點(diǎn),熟悉一遍useCallback的調(diào)用流程。(由于.gif過(guò)大,這里就不上git了,自行調(diào)試)

源碼解析
useCallback的整體流程框架
在react中mount階段和update階段進(jìn)入到同一個(gè)useCallback方法里。但resolveDispatcher找到的dispatch對(duì)象mount和update會(huì)不同,最終導(dǎo)致在mount階段調(diào)用mountCallback而update階段調(diào)用的是updateCallback。
下面為調(diào)用useCallback方法觸發(fā)的行為
function useCallback(callback, deps) {
var dispatcher = resolveDispatcher();
return dispatcher.useCallback(callback, deps);
}下面來(lái)看看resolveDispatcher是如何獲取到dispatch的
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current;
...
return ((dispatcher: any): Dispatcher);
}ReactCurrentDispatcher.current會(huì)在renderWithHooks方法中進(jìn)行所處階段判斷并且賦值。如果current === null || current.memoizedState === null為true表示在mount階段反正為update階段
function renderWithHooks<Props, SecondArg>(...) {
...
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
}
// mount階段調(diào)用的dispatch
const HooksDispatcherOnMount: Dispatcher = {
...
useCallback: mountCallback,
};
// update階段調(diào)用的dispatch
const HooksDispatcherOnUpdate: Dispatcher = {
...
useCallback: updateCallback,
};從上面的代碼分析可以知道在mounted階段調(diào)用的是mountCallback在update階段調(diào)用updateCallback
Hook
一個(gè)函數(shù)式組件鏈路: fiber(FunctionComponent) => Hook(保存數(shù)據(jù)狀態(tài)) => Queue(更新的隊(duì)列結(jié)構(gòu)) => update(更新的數(shù)據(jù))在后續(xù)需要使用到Hook這個(gè)結(jié)構(gòu),那么先來(lái)看一下Hook是數(shù)據(jù)結(jié)構(gòu)是怎么樣的,以及屬性的作用是什么?
- memoizedState 存放的是Hook對(duì)應(yīng)的state
- next鏈接到下一個(gè)Hook,從而形成一個(gè)
無(wú)環(huán)單向鏈表 - queue存儲(chǔ)同一個(gè)hook更新的多個(gè)update對(duì)象,數(shù)據(jù)結(jié)構(gòu)為
環(huán)狀單向鏈表
// 組件對(duì)應(yīng)的fiber對(duì)象
const fiber = {
// 保存該FunctionComponent對(duì)應(yīng)的Hooks鏈表
memoizedState: hook,
...
};
const hook: Hook = {
// 1. memoizedState 存放的是Hook對(duì)應(yīng)的state
memoizedState: null,
// 2. next鏈接到下一個(gè)Hook,從而形成一個(gè)`無(wú)環(huán)單向鏈表`
queue: null,
// 3. queue存儲(chǔ)同一個(gè)hook更新的多個(gè)update對(duì)象,數(shù)據(jù)結(jié)構(gòu)為`環(huán)狀單向鏈表`
next: null,
...
};fiber與Hooks的關(guān)系(懶得畫(huà)圖了,引用了Understanding the Closure Trap of React Hooks)

mount階段
分析mountCallback的實(shí)現(xiàn)
- 通過(guò)
mountWorkInProgressHook獲取到對(duì)應(yīng)的Hook對(duì)象 - 判斷條件deps是否為undefined
- 將
回調(diào)函數(shù)和判斷條件存入到hook.memoizedState - 返回傳入的回調(diào)函數(shù)
function mountCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}mountWorkInProgressHook的實(shí)現(xiàn),創(chuàng)建初始化Hook對(duì)象,并且將該Hook對(duì)象保存在workInProgressHook鏈路中. workInProgressHook表示正在執(zhí)行的hook
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}在組件render時(shí),每當(dāng)遇到下一個(gè)Hook,通過(guò)移動(dòng)workInProgressHook的指針來(lái)獲取到對(duì)應(yīng)的HookPS: 只要每次組件render時(shí)useState的調(diào)用順序及數(shù)量保持一致,那么始終可以通過(guò)workInProgressHook找到當(dāng)前useState對(duì)應(yīng)的hook對(duì)象
// fiber.memoizedState標(biāo)識(shí)第一個(gè)Hook workInProgressHook = fiber.memoizedState; // 在組件`render`時(shí),遇到下一個(gè)hook時(shí) workInProgressHook = workInProgressHook.next; ....
update階段
分析updateCallback的實(shí)現(xiàn)
- 通過(guò)
updateWorkInProgressHook獲取到當(dāng)前的Hook對(duì)象 hook.memoizedState獲取到上一次緩存的state。假設(shè)這是第一次update那么其值就是mount階段保存的[callback, nextDeps]數(shù)據(jù)- 如果依賴(lài)條件不為空,使用
areHookInputsEqual判斷依賴(lài)項(xiàng)是否更改。只會(huì)遍歷數(shù)組第一層數(shù)據(jù)比較不會(huì)做深層比較。如果依賴(lài)項(xiàng)沒(méi)變化,返回原本緩存的callback。
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const prevState = hook.memoizedState;
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) {
return prevState[0];
}
}
hook.memoizedState = [callback, nextDeps];
return callback;
}依賴(lài)比較areHookInputsEqual的方法實(shí)現(xiàn)
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
): boolean {
...
// $FlowFixMe[incompatible-use] found when upgrading Flow
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}總結(jié)
在React中會(huì)使用閉包機(jī)制來(lái)處理上文的callback回調(diào)函數(shù)。當(dāng)包含useCallback組件被渲染時(shí),React 會(huì)為該特定渲染周期創(chuàng)建一個(gè)閉包。閉包是一個(gè)封裝的作用域,其中包含渲染時(shí)位于作用域內(nèi)的變量、函數(shù)和其他引用。
因此deps我們傳入的是空數(shù)組,其回調(diào)函數(shù)callback一直引用的狀態(tài)始終是初始狀態(tài),無(wú)法獲取最新?tīng)顟B(tài)。緩存的回調(diào)函數(shù)可以訪問(wèn)最初調(diào)用時(shí)范圍內(nèi)的狀態(tài)和道具
插件推薦
閱讀源碼可以通過(guò)使用Bookmarks快速標(biāo)記代碼位置,實(shí)現(xiàn)快速條件

到此這篇關(guān)于詳解react中useCallback內(nèi)部是如何實(shí)現(xiàn)的的文章就介紹到這了,更多相關(guān)react useCallback內(nèi)部實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 關(guān)于react中useCallback的用法
- react中關(guān)于useCallback的用法
- React Hooks之使用useCallback和useMemo進(jìn)行性能優(yōu)化方式
- React?useCallback使用方法詳解
- React中memo useCallback useMemo方法作用及使用場(chǎng)景
- 解析React中useMemo與useCallback的區(qū)別
- react組件memo useMemo useCallback使用區(qū)別示例
- React?中?memo?useMemo?useCallback?到底該怎么用
- React中useCallback 的基本使用和原理小結(jié)
相關(guān)文章
React中異步數(shù)據(jù)更新不及時(shí)問(wèn)題及解決
這篇文章主要介紹了React中異步數(shù)據(jù)更新不及時(shí)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
react中使用Modal.confirm數(shù)據(jù)不更新的問(wèn)題完美解決方案
這篇文章主要介紹了react中使用Modal.confirm數(shù)據(jù)不更新的問(wèn)題解決方案,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
React函數(shù)組件和類(lèi)組件的區(qū)別及說(shuō)明
這篇文章主要介紹了React函數(shù)組件和類(lèi)組件的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
使用webpack搭建react開(kāi)發(fā)環(huán)境的方法
本篇文章主要介紹了使用webpack搭建react開(kāi)發(fā)環(huán)境的方法,在這篇文章中我們開(kāi)始利用我們之前所學(xué)搭建一個(gè)簡(jiǎn)易的React開(kāi)發(fā)環(huán)境,用以鞏固我們之前學(xué)習(xí)的Webpack知識(shí)。一起跟隨小編過(guò)來(lái)看看吧2018-05-05
React Native預(yù)設(shè)占位placeholder的使用
本篇文章主要介紹了React Native預(yù)設(shè)占位placeholder的使用,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
React項(xiàng)目中使用Redux的?react-redux
這篇文章主要介紹了React項(xiàng)目中使用Redux的?react-redux,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09
使用 React Router Dom 實(shí)現(xiàn)路由導(dǎo)航的詳細(xì)過(guò)程
React Router Dom 是 React 應(yīng)用程序中用于處理路由的常用庫(kù),它提供了一系列組件和 API 來(lái)管理應(yīng)用程序的路由,這篇文章主要介紹了使用 React Router Dom 實(shí)現(xiàn)路由導(dǎo)航,需要的朋友可以參考下2024-03-03

