ReactDOM 隱藏特性詳解
前言
有過(guò) React 經(jīng)驗(yàn)的開發(fā)者可能都使用過(guò) React DevTools。
DevTools 提供了豐富的能力:展示組件樹,組件的 props 與組件中 hook 的值。
React DevTools 是如何檢測(cè)當(dāng)前網(wǎng)頁(yè)是否使用 React 以及是如何獲取組件相關(guān)的眾多數(shù)據(jù)呢?
React DevTools 的原理
打開 ReactDOM 代碼時(shí),用 devtools
為關(guān)鍵字搜索,你會(huì)發(fā)現(xiàn)許多與 React DevTools 相關(guān)的代碼。
function injectInternals(internals) { if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { // No DevTools return false; } var hook = __REACT_DEVTOOLS_GLOBAL_HOOK__; try { rendererID = hook.inject(internals); // We have successfully injected, so now it is safe to set up hooks. injectedHook = hook; } catch (err) { // ... } // DevTools exists }
在瀏覽器控制臺(tái)輸入 __REACT_DEVTOOLS_GLOBAL_HOOK__
詳細(xì)看一下這個(gè)對(duì)象。
這個(gè)對(duì)象十分復(fù)雜,以下的幾個(gè)方法倒是很值得關(guān)注。
onCommitFiberRoot
onCommitFiberUnmount
onPostCommitFiberRoot
渲染階段
從名稱來(lái)看,上面這幾個(gè)方法與 ReactDOM 的渲染密切相關(guān)。
ReactDOM 在特定的階段會(huì)調(diào)用這些的方法,比如:onCommitFiberRoot
。
function onCommitRoot(root, priorityLevel) { if (injectedHook && typeof injectedHook.onCommitFiberRoot === 'function') { try { // ... injectedHook.onCommitFiberRoot(rendererID, root, priorityLevel, didError); } catch (err) {} } }
正是借助 __REACT_DEVTOOLS_GLOBAL_HOOK__
,React DevTools 便與 ReactDOM 建立起了聯(lián)系,從而擁有獲取組件眾多信息的能力。
FiberRoot/FiberNode
在新的 React 架構(gòu)下,會(huì)先把 Virtual DOM 轉(zhuǎn)成 FiberNode,然后再渲染 FiberNode。
onCommitFiberRoot
等方法中的傳遞的數(shù)據(jù)正是 FiberNode。
FiberNode 的結(jié)構(gòu)是比較復(fù)雜的,可以簡(jiǎn)化為如下的結(jié)構(gòu):
interface ReactFiberRootNode { current: ReactFiberNode; // ... } interface ReactFiberNode { tag: number; stateNode: null | HTMLElement; // dom 節(jié)點(diǎn) memoizedProps?: Record<string, any>; // props memoizedState: ClassComponentState | HookLinkedQueue | null; // hooks child?: ReactFiberNode; sibling?: ReactFiberNode; return: ReactFiberNode; // parent // ... }
從上面的結(jié)構(gòu)可以看出,F(xiàn)iberNode 包含了非常多與組件相關(guān)的信息。
stateNode
為組件對(duì)應(yīng)真實(shí)的 DOM 節(jié)點(diǎn),memoizedProps
為組件的 props
。
當(dāng)組件為函數(shù)式組件時(shí),tag
為 0,memoizedState
保存了組件中的 hooks 信息。
當(dāng)組件為類組件時(shí),tag
為 1,memoizedState
則是組件的 state
。
如下圖所示,F(xiàn)iberNode 節(jié)點(diǎn)形成一個(gè)鏈表結(jié)構(gòu)。
只要能找到組件對(duì)應(yīng)的 FiberNode,我們便可以做到在運(yùn)行期間以無(wú)侵入的方法獲取組件的眾多信息。比如:通過(guò) FiberNode 進(jìn)行遍歷,實(shí)現(xiàn) findNativeNodesForFiber
方法,用以查找其對(duì)應(yīng)的真實(shí) DOM 節(jié)點(diǎn)。
function findNativeNodesForFiber(node?: ReactFiberNode) { // ... // 先遍歷 child const { child } = node; collectStateNode(); // 再遍歷所有的 sibling let current = child?.sibling; while (current) { collectStateNode(); current = current.sibling; } // ... }
React DevTools 中審查元素功能正是基于類似的原理去實(shí)現(xiàn)。
memoizedState 與 React Hooks
上文中提到當(dāng)組件為函數(shù)式組件時(shí),memoizedState
保存了 React Hooks 相關(guān)的信息。與 FiberNode 類似,React Hooks 也形成一個(gè)鏈表。
export interface HookLinkedQueue { memoizedState: any; // 渲染時(shí)的值 next: HookLinkedQueue | null; // ... }
React Hook 將其數(shù)據(jù)都保存在 memoizedState
上。比如對(duì)于 useRef
來(lái)說(shuō),ref.current
值就是 memoizedState
。類似的,可以實(shí)現(xiàn) inspectSomeHooksOfFiber
來(lái)獲取組件內(nèi)使用特定 hook 中保存的值。
function inspectRefHooksOfFiber(node: ReactFiberNode) { let current: HookLinkedQueue | null = node.memoizedState; while (current) { retrieveValue(current); current = current.next; } }
實(shí)踐:突破 useDebugValue 的限制
useDebugValue 是 React 內(nèi)置的一個(gè) hook,用以在 React DevTools 中顯示自定義 hook 的標(biāo)簽。它的限制是只能在 hook 中使用。借助前文介紹的知識(shí)點(diǎn),我們可以實(shí)現(xiàn)一個(gè)增加版的 useDebugValue
,你可以像普通的 hook 一樣來(lái)使用它,沒(méi)有其他限制。
useDebugValueAnywhere 的實(shí)現(xiàn)
useDebugValueAnywhere
實(shí)現(xiàn)比較簡(jiǎn)單,name
表明數(shù)據(jù)的名稱,用一個(gè)特殊的 ref 對(duì)象來(lái)存儲(chǔ) debug 相關(guān)的數(shù)據(jù)。
export function useDebugValueAnywhere(name: string, data: any) { const ref = useRef({ [DebugHookKey]: { name, data, }, }); // ... }
特定的 devtools
參考 React DevTools 的邏輯,在 __REACT_DEVTOOLS_GLOBAL_HOOK__
中注入我們的 onCommitFiberRoot
方法,從而確保 ReactDOM 每次渲染時(shí),能獲取最新的 FiberNode。
currentHook.onCommitFiberRoot = function (...args) { handleCommitFiberRoot(...args); // 注入 oldOnCommitFiberRoot.apply(this, args); };
接下來(lái)便是對(duì) FiberNode 進(jìn)行遍歷。在遍歷的過(guò)程中,檢查每個(gè) FiberNode 中 memoizedState
鏈表,檢測(cè)組件的 hooks 中是否用到了 useDebugValueAnywhere
。
如果存在,就將值 FiberNode 與 hook 中的值保存起來(lái)。
{ visitFiberNode(node?: ReactFiberNode) { if (!node) return; this.inspectFiber(node); this.visitFiberNode(node.child); let { sibling } = node; while (sibling) { this.visitFiberNode(sibling); sibling = sibling.sibling; } } }
剩下的工作就是考慮以何種形式去展示收集到的 debug 信息。在 PC 端可以直接輸出數(shù)據(jù)到控制臺(tái);在移動(dòng)端 vConsole 使用較多,那么就可以基于 vConsole 開發(fā)一個(gè)插件,實(shí)現(xiàn)一個(gè)極簡(jiǎn)版的 React DevTools,專門用以展示這些信息。
總結(jié)
本文剖析了 React DevTools 的原理,介紹隱藏在 ReactDOM 中的一些特性,并帶領(lǐng)大家熟悉了一下 React Fiber 架構(gòu)?;谏鲜鲈?,可以開發(fā)一個(gè)增加版的 useDebugValue
。
由于本文介紹的特性并非公開的 API,沒(méi)有兼容性。當(dāng) React/ReactDOM 版本升級(jí)時(shí),還需要再做適配,因此只適合用來(lái)開發(fā) DevTools 之類的工具,不推薦業(yè)務(wù)開發(fā)使用。
以上就是ReactDOM 隱藏特性詳解的詳細(xì)內(nèi)容,更多關(guān)于ReactDOM 隱藏特性的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React創(chuàng)建組件的三種方式及其區(qū)別是什么
在React中,創(chuàng)建組件的三種主要方式是函數(shù)式組件、類組件和使用React Hooks的函數(shù)式組件,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解一下2023-08-08react ant protable自定義實(shí)現(xiàn)搜索下拉框
這篇文章主要介紹了react ant protable自定義實(shí)現(xiàn)搜索下拉框,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06再次談?wù)揜eact.js實(shí)現(xiàn)原生js拖拽效果引起的一系列問(wèn)題
React 起源于 Facebook 的內(nèi)部項(xiàng)目,因?yàn)樵摴緦?duì)市場(chǎng)上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來(lái)架設(shè) Instagram 的網(wǎng)站.本文給大家介紹React.js實(shí)現(xiàn)原生js拖拽效果,需要的朋友一起學(xué)習(xí)吧2016-04-04關(guān)于useEffect執(zhí)行兩次的問(wèn)題及解決
這篇文章主要介紹了關(guān)于useEffect執(zhí)行兩次的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09React?Native集成支付寶支付的實(shí)現(xiàn)方法
這篇文章主要介紹了React?Native集成支付寶支付的實(shí)現(xiàn)現(xiàn),ativeModules是JS代碼調(diào)用原生模塊的橋梁。所以,我們只需要在原生工程中集成支付寶和微信支付的sdk,然后使用NativeModules調(diào)用即可,需要的朋友可以參考下2022-02-02React通過(guò)conetxt實(shí)現(xiàn)多組件傳值功能
Context 提供了一種在組件之間共享此類值的方式,而不必顯式地通過(guò)組件樹的逐層傳遞 props。本文給大家介紹React通過(guò)conetxt實(shí)現(xiàn)多組件傳值功能,感興趣的朋友一起看看吧2021-10-10react-redux多個(gè)組件數(shù)據(jù)共享的方法
這篇文章主要介紹了react-redux多個(gè)組件數(shù)據(jù)共享的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08