Secret Behind React Devtools中文

lang
中文
date
Dec 21, 2022
Property
slug
react-devtools
status
Published
tags
React
Theory
summary
type
Post

前言

有过 React 经验的开发者可能都使用过 React DevTools。
DevTools 提供了丰富的能力:展示组件树,组件的 props 与组件中 hook 的值。
React DevTools 是如何检测当前网页是否使用 React 以及是如何获取组件相关的众多数据呢?
notion image

React DevTools 的原理

打开 ReactDOM 代码时,用 devtools 为关键字搜索,你会发现许多与 React DevTools 相关的代码。
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 }
在浏览器控制台输入 __REACT_DEVTOOLS_GLOBAL_HOOK__ 详细看一下这个对象。
notion image
这个对象十分复杂,以下的几个方法倒是很值得关注。
onCommitFiberRoot
onCommitFiberUnmount
onPostCommitFiberRoot

渲染阶段

从名称来看,上面这几个方法与 ReactDOM 的渲染密切相关。
ReactDOM 在特定的阶段会调用这些的方法,比如: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 建立起了联系,从而拥有获取组件众多信息的能力。

FiberRoot/FiberNode

在新的 React 架构下,会先把 Virtual DOM 转成 FiberNode,然后再渲染 FiberNode。
onCommitFiberRoot 等方法中的传递的数据正是 FiberNode。
FiberNode 的结构是比较复杂的,可以简化为如下的结构:
interface ReactFiberRootNode { current: ReactFiberNode; // ... } interface ReactFiberNode { tag: number; stateNode: null | HTMLElement; // dom 节点 memoizedProps?: Record<string, any>; // props memoizedState: ClassComponentState | HookLinkedQueue | null; // hooks child?: ReactFiberNode; sibling?: ReactFiberNode; return: ReactFiberNode; // parent // ... }
从上面的结构可以看出,FiberNode 包含了非常多与组件相关的信息。
stateNode 为组件对应真实的 DOM 节点,memoizedProps 为组件的 props
当组件为函数式组件时,tag 为 0,memoizedState 保存了组件中的 hooks 信息。
当组件为类组件时,tag 为 1,memoizedState 则是组件的 state
如下图所示,FiberNode 节点形成一个链表结构。
notion image
只要能找到组件对应的 FiberNode,我们便可以做到在运行期间以无侵入的方法获取组件的众多信息。比如:通过 FiberNode 进行遍历,实现 findNativeNodesForFiber 方法,用以查找其对应的真实 DOM 节点。
function findNativeNodesForFiber(node?: ReactFiberNode) { // ... // 先遍历 child const { child } = node; collectStateNode(); // 再遍历所有的 sibling let current = child?.sibling; while (current) { collectStateNode(); current = current.sibling; } // ... }
React DevTools 中审查元素功能正是基于类似的原理去实现。
notion image

memoizedState 与 React Hooks

上文中提到当组件为函数式组件时,memoizedState 保存了 React Hooks 相关的信息。与 FiberNode 类似,React Hooks 也形成一个链表。
export interface HookLinkedQueue { memoizedState: any; // 渲染时的值 next: HookLinkedQueue | null; // ... }
React Hook 将其数据都保存在 memoizedState 上。比如对于 useRef 来说,ref.current 值就是 memoizedState。类似的,可以实现 inspectSomeHooksOfFiber 来获取组件内使用特定 hook 中保存的值。
function inspectRefHooksOfFiber(node: ReactFiberNode) { let current: HookLinkedQueue | null = node.memoizedState; while (current) { retrieveValue(current); current = current.next; } }

© Matoz 2021 - 2024