ReactDOM 隐藏特性详解

 更新时间:2022年09月01日 15:45:17   作者:货拉拉技术  
这篇文章主要为大家介绍了ReactDOM 隐藏特性详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

有过 React 经验的开发者可能都使用过 React DevTools。

DevTools 提供了丰富的能力:展示组件树,组件的 props 与组件中 hook 的值。

React DevTools 是如何检测当前网页是否使用 React 以及是如何获取组件相关的众多数据呢?

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__ 详细看一下这个对象。

这个对象十分复杂,以下的几个方法倒是很值得关注。

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 节点形成一个链表结构。

只要能找到组件对应的 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 中审查元素功能正是基于类似的原理去实现。

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;
  }
}

实践:突破 useDebugValue 的限制

useDebugValue 是 React 内置的一个 hook,用以在 React DevTools 中显示自定义 hook 的标签。它的限制是只能在 hook 中使用。借助前文介绍的知识点,我们可以实现一个增加版的 useDebugValue,你可以像普通的 hook 一样来使用它,没有其他限制。

useDebugValueAnywhere 的实现

useDebugValueAnywhere 实现比较简单,name 表明数据的名称,用一个特殊的 ref 对象来存储 debug 相关的数据。

export function useDebugValueAnywhere(name: string, data: any) {
  const ref = useRef({
    [DebugHookKey]: {
      name,
      data,
    },
  });
  // ...
}

特定的 devtools

参考 React DevTools 的逻辑,在 __REACT_DEVTOOLS_GLOBAL_HOOK__ 中注入我们的 onCommitFiberRoot 方法,从而确保 ReactDOM 每次渲染时,能获取最新的 FiberNode。

currentHook.onCommitFiberRoot = function (...args) {
  handleCommitFiberRoot(...args); // 注入
  oldOnCommitFiberRoot.apply(this, args);
};

接下来便是对 FiberNode 进行遍历。在遍历的过程中,检查每个 FiberNode 中 memoizedState 链表,检测组件的 hooks 中是否用到了 useDebugValueAnywhere

如果存在,就将值 FiberNode 与 hook 中的值保存起来。

{
  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 端可以直接输出数据到控制台;在移动端 vConsole 使用较多,那么就可以基于 vConsole 开发一个插件,实现一个极简版的 React DevTools,专门用以展示这些信息。

完整的代码

总结

本文剖析了 React DevTools 的原理,介绍隐藏在 ReactDOM 中的一些特性,并带领大家熟悉了一下 React Fiber 架构。基于上述原理,可以开发一个增加版的 useDebugValue

由于本文介绍的特性并非公开的 API,没有兼容性。当 React/ReactDOM 版本升级时,还需要再做适配,因此只适合用来开发 DevTools 之类的工具,不推荐业务开发使用。

以上就是ReactDOM 隐藏特性详解的详细内容,更多关于ReactDOM 隐藏特性的资料请关注脚本之家其它相关文章!

相关文章

  • React创建组件的三种方式及其区别是什么

    React创建组件的三种方式及其区别是什么

    在React中,创建组件的三种主要方式是函数式组件、类组件和使用React Hooks的函数式组件,本文就详细的介绍一下如何使用,感兴趣的可以了解一下
    2023-08-08
  • react ant protable自定义实现搜索下拉框

    react ant protable自定义实现搜索下拉框

    这篇文章主要介绍了react ant protable自定义实现搜索下拉框,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • 再次谈论React.js实现原生js拖拽效果引起的一系列问题

    再次谈论React.js实现原生js拖拽效果引起的一系列问题

    React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instagram 的网站.本文给大家介绍React.js实现原生js拖拽效果,需要的朋友一起学习吧
    2016-04-04
  • 关于useEffect执行两次的问题及解决

    关于useEffect执行两次的问题及解决

    这篇文章主要介绍了关于useEffect执行两次的问题及解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • react-router4 嵌套路由的使用方法

    react-router4 嵌套路由的使用方法

    本篇文章主要介绍了react-router4 嵌套路由的使用方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • React Native集成支付宝支付的实现方法

    React Native集成支付宝支付的实现方法

    这篇文章主要介绍了React Native集成支付宝支付的实现现,ativeModules是JS代码调用原生模块的桥梁。所以,我们只需要在原生工程中集成支付宝和微信支付的sdk,然后使用NativeModules调用即可,需要的朋友可以参考下
    2022-02-02
  • webpack入门+react环境配置

    webpack入门+react环境配置

    webpack是一个前端资源模块化管理和打包工具,说白了就是方便我们管理自己的常用的一些代码,比如你开发中用到sass以及jade同时用到es6,开发时你不可能改动某个地方就挨个命令去转换再到浏览器去看效果,那样效率是非常低的。所以webpack帮我们省去了那些多余的步骤。
    2017-02-02
  • React通过conetxt实现多组件传值功能

    React通过conetxt实现多组件传值功能

    Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。本文给大家介绍React通过conetxt实现多组件传值功能,感兴趣的朋友一起看看吧
    2021-10-10
  • es6在react中的应用代码解析

    es6在react中的应用代码解析

    这篇文章主要介绍了es6在react中的应用代码解析,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-11-11
  • react-redux多个组件数据共享的方法

    react-redux多个组件数据共享的方法

    这篇文章主要介绍了react-redux多个组件数据共享的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08

最新评论