詳解react中useCallback內(nèi)部是如何實現(xiàn)的
示例demo與debug
新建了一個react項目,將APP.tsx改寫成如下代碼
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過大,這里就不上git了,自行調(diào)試)
源碼解析
useCallback的整體流程框架
在react中mount階段和update階段進(jìn)入到同一個useCallback方法里。但resolveDispatcher找到的dispatch對象mount
和update
會不同,最終導(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); }
下面來看看resolveDispatcher
是如何獲取到dispatch的
function resolveDispatcher() { const dispatcher = ReactCurrentDispatcher.current; ... return ((dispatcher: any): Dispatcher); }
ReactCurrentDispatcher.current
會在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
一個函數(shù)式組件鏈路: fiber(FunctionComponent) => Hook(保存數(shù)據(jù)狀態(tài)) => Queue(更新的隊列結(jié)構(gòu)) => update(更新的數(shù)據(jù))
在后續(xù)需要使用到Hook這個結(jié)構(gòu),那么先來看一下Hook是數(shù)據(jù)結(jié)構(gòu)是怎么樣的,以及屬性的作用是什么?
- memoizedState 存放的是Hook對應(yīng)的state
- next鏈接到下一個Hook,從而形成一個
無環(huán)單向鏈表
- queue存儲同一個hook更新的多個update對象,數(shù)據(jù)結(jié)構(gòu)為
環(huán)狀單向鏈表
// 組件對應(yīng)的fiber對象 const fiber = { // 保存該FunctionComponent對應(yīng)的Hooks鏈表 memoizedState: hook, ... }; const hook: Hook = { // 1. memoizedState 存放的是Hook對應(yīng)的state memoizedState: null, // 2. next鏈接到下一個Hook,從而形成一個`無環(huán)單向鏈表` queue: null, // 3. queue存儲同一個hook更新的多個update對象,數(shù)據(jù)結(jié)構(gòu)為`環(huán)狀單向鏈表` next: null, ... };
fiber與Hooks的關(guān)系(懶得畫圖了,引用了Understanding the Closure Trap of React Hooks
)
mount階段
分析mountCallback
的實現(xiàn)
- 通過
mountWorkInProgressHook
獲取到對應(yīng)的Hook對象 - 判斷條件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
的實現(xiàn),創(chuàng)建初始化Hook
對象,并且將該Hook對象
保存在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
時,每當(dāng)遇到下一個Hook
,通過移動workInProgressHook
的指針來獲取到對應(yīng)的Hook
PS: 只要每次組件render
時useState
的調(diào)用順序及數(shù)量保持一致,那么始終可以通過workInProgressHook
找到當(dāng)前useState
對應(yīng)的hook
對象
// fiber.memoizedState標(biāo)識第一個Hook workInProgressHook = fiber.memoizedState; // 在組件`render`時,遇到下一個hook時 workInProgressHook = workInProgressHook.next; ....
update階段
分析updateCallback
的實現(xiàn)
- 通過
updateWorkInProgressHook
獲取到當(dāng)前的Hook對象 hook.memoizedState
獲取到上一次緩存的state
。假設(shè)這是第一次update
那么其值就是mount階段
保存的[callback, nextDeps]
數(shù)據(jù)- 如果依賴條件不為空,使用
areHookInputsEqual
判斷依賴項是否更改。只會遍歷數(shù)組第一層數(shù)據(jù)比較
不會做深層比較。如果依賴項沒變化,返回原本緩存的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; }
依賴比較areHookInputsEqual
的方法實現(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中會使用閉包機(jī)制
來處理上文的callback
回調(diào)函數(shù)。當(dāng)包含useCallback
組件被渲染時,React 會為該特定渲染周期創(chuàng)建一個閉包。閉包是一個封裝的作用域,其中包含渲染時位于作用域內(nèi)的變量、函數(shù)和其他引用
。
因此deps我們傳入的是空數(shù)組,其回調(diào)函數(shù)callback一直引用的狀態(tài)始終是初始狀態(tài),無法獲取最新狀態(tài)
。緩存的回調(diào)函數(shù)可以訪問最初調(diào)用時范圍內(nèi)的狀態(tài)和道具
插件推薦
閱讀源碼可以通過使用Bookmarks
快速標(biāo)記代碼位置,實現(xiàn)快速條件
到此這篇關(guān)于詳解react中useCallback內(nèi)部是如何實現(xiàn)的的文章就介紹到這了,更多相關(guān)react useCallback內(nèi)部實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?Hook中的useState函數(shù)的詳細(xì)解析
Hook 就是 JavaScript 函數(shù),這個函數(shù)可以幫助你鉤入(hook into) React State以及生命周期等特性,這篇文章主要介紹了React?Hook?useState函數(shù)的詳細(xì)解析的相關(guān)資料,需要的朋友可以參考下2022-10-10深入理解React Native原生模塊與JS模塊通信的幾種方式
本篇文章主要介紹了深入理解React Native原生模塊與JS模塊通信的幾種方式,具有一定的參考價值,有興趣的可以了解一下2017-07-07React Router 如何使用history跳轉(zhuǎn)的實現(xiàn)
這篇文章主要介紹了React Router 如何使用history跳轉(zhuǎn)的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04React如何利用Antd的Form組件實現(xiàn)表單功能詳解
這篇文章主要給大家介紹了關(guān)于React如何利用Antd的Form組件實現(xiàn)表單功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04