【问题标题】:React - getting a component from a DOM element for debuggingReact - 从 DOM 元素中获取组件以进行调试
【发布时间】:2015-06-02 00:34:52
【问题描述】:

为了在控制台中进行调试,React 中是否有任何机制可以使用 DOM 元素实例来获取支持的 React 组件?

这个问题之前已经在生产代码中使用它的上下文中被问过。但是,我的重点是用于调试目的的开发构建。

我熟悉Chrome debugging extension for React,但并非所有浏览器都提供此功能。结合 DOM 浏览器和控制台,可以很容易地使用“$0”快捷方式来访问有关突出显示的 DOM 元素的信息。

我想在调试控制台中编写类似这样的代码: getComponentFromElement($0).props

即使在 React 开发构建中,是否也没有机制可以使用元素的 ReactId 来获取组件?

【问题讨论】:

  • 已编辑问题以解释与之前提出的问题的区别。
  • @WiredPrairie 这实际上有点不同 - 它更多的是关于树过滤而不是简单地获取元素。
  • @LodeRunner28 检查我的答案

标签: javascript reactjs


【解决方案1】:

我刚刚通读了文档,并且 afaik 没有任何外部公开的 API 可以让您直接进入并通过 ID 查找 React 组件。但是,您可以更新您的初始 React.render() 调用并将返回值保留在某处,例如:

window.searchRoot = React.render(React.createElement......

然后您可以引用 searchRoot,并直接查看它,或使用 React.addons.TestUtils 遍历它。例如这将为您提供所有组件:

var componentsArray = React.addons.TestUtils.findAllInRenderedTree(window.searchRoot, function() { return true; });

有几种内置方法可以过滤此树,或者您可以编写自己的函数以仅根据您编写的某些检查返回组件。

更多关于 TestUtils 的信息在这里:https://facebook.github.io/react/docs/test-utils.html

【讨论】:

  • 完美。 TestUtils 机制提供了我正在寻找的东西。现在在我的调试版本中,我可以提供一个全局函数,我可以在控制台中使用它来搜索返回的组件列表并将其与所选元素匹配。
【解决方案2】:

这是我目前正在使用的一个小型 sn-p。

它适用于 React 0.14.7。

Gist with the code

let searchRoot = ReactDom.render(ROOT, document.getElementById('main'));

var getComponent = (comp) => comp._renderedComponent ? getComponent(comp._renderedComponent) : comp;

var getComponentById = (id)=> {
  var comp = searchRoot._reactInternalInstance;
  var path = id.substr(1).split('.').map(a=> '.' + a);
  if (comp._rootNodeID !== path.shift()) throw 'Unknown root';
  while (path.length > 0) {
    comp = getComponent(comp)._renderedChildren[path.shift()];
  }
  return comp._instance;
};

window.$r = (node)=> getComponentById(node.getAttribute('data-reactid'))

要运行它,打开 Devtools,突出显示要检查的元素,然后在控制台中输入:$r($0)

【讨论】:

    【解决方案3】:

    我写了这个小技巧来允许从它的 dom 节点访问任何 react 组件

    var ReactDOM = require('react-dom');
    (function () {
        var _render = ReactDOM.render;
        ReactDOM.render = function () {
            return arguments[1].react = _render.apply(this, arguments);
        };
    })();
    

    然后您可以直接使用以下方式访问任何组件:

    document.getElementById("lol").react
    

    或使用 JQuery

    $("#lol").get(0).react
    

    【讨论】:

    • 这太棒了,谢谢!例如,假设我使用了这段代码,并想设置一个反应组件的道具,使用普通的 javascript,我将如何做出反应来重绘组件?例如,假设我有 element.react.props.config.sunhat.enabled=false;那么我怎样才能让该元素更新/渲染?我试过 element.react.render() 但这似乎不起作用,有什么想法吗?
    【解决方案4】:

    这是我使用的助手:(已更新为适用于 React

    function FindReact(dom, traverseUp = 0) {
        const key = Object.keys(dom).find(key=>{
            return key.startsWith("__reactFiber$") // react 17+
                || key.startsWith("__reactInternalInstance$"); // react <17
        });
        const domFiber = dom[key];
        if (domFiber == null) return null;
    
        // react <16
        if (domFiber._currentElement) {
            let compFiber = domFiber._currentElement._owner;
            for (let i = 0; i < traverseUp; i++) {
                compFiber = compFiber._currentElement._owner;
            }
            return compFiber._instance;
        }
    
        // react 16+
        const GetCompFiber = fiber=>{
            //return fiber._debugOwner; // this also works, but is __DEV__ only
            let parentFiber = fiber.return;
            while (typeof parentFiber.type == "string") {
                parentFiber = parentFiber.return;
            }
            return parentFiber;
        };
        let compFiber = GetCompFiber(domFiber);
        for (let i = 0; i < traverseUp; i++) {
            compFiber = GetCompFiber(compFiber);
        }
        return compFiber.stateNode;
    }
    

    用法:

    const someElement = document.getElementById("someElement");
    const myComp = FindReact(someElement);
    myComp.setState({test1: test2});
    

    注意:此版本比其他答案更长,因为它包含从直接包装 dom-node 的组件进行遍历的代码。 (如果没有此代码,FindReact 函数会在某些常见情况下失败,如下所示)

    绕过中间组件

    假设您要查找的组件 (MyComp) 如下所示:

    class MyComp extends Component {
        render() {
            return (
                <InBetweenComp>
                    <div id="target">Element actually rendered to dom-tree.</div>
                </InBetweenComp>
            );
        }
    }
    

    在这种情况下,调用 FindReact(target) 将(默认情况下)返回 InBetweenComp 实例,因为它是 dom 元素的第一个组件祖先。

    要解决此问题,请增加 traverseUp 参数,直到找到所需的组件:

    const target = document.getElementById("target");
    const myComp = FindReact(target, 1);   // provide traverse-up distance here
    

    有关遍历 React 组件树的更多详细信息,see here

    功能组件

    函数组件不像类那样有“实例”,所以你不能只修改FindReact函数来返回一个带有forceUpdatesetState等的对象作为函数组件。

    也就是说,您至少可以获得该路径的 React-fiber 节点,包含它的 props、状态等。为此,请将FindReact 函数的最后一行修改为:return compFiber;

    【讨论】:

    • 当修补render 不是一个选项时,这是解决方案(例如通过脚本注入增强页面)。
    • 很棒的解决方案,对我帮助很大
    • (您可以通过FindReact($0) 来获取通过右键单击> 检查 选择的元素)
    • 太棒了!做到别人说的话是不可能的!
    • 它在控制台上对我有用,但是当我尝试从我的 chrome 扩展程序中使用它时,Object.keys 对于同一个 htmlnode 是空的。有什么建议吗?
    【解决方案5】:

    我已经对@Venryx 的答案进行了稍微修改的 ES6 版本,以满足我的需求。此辅助函数返回当前元素而不是 _owner._instance 属性。

    getReactDomComponent(dom) {
      const internalInstance = dom[Object.keys(dom).find(key =>
        key.startsWith('__reactInternalInstance$'))];
      if (!internalInstance) return null;
      return internalInstance._currentElement;
    }
    

    【讨论】:

    • ES6 版本更好更短。但是,我很好奇,您对“_currentElement”对象本身有什么用处?它没有大多数人想要访问的“setState”之类的正常功能。
    • @Venryx 我们经常使用 _currentElement 的 props 属性来测试断言。当浅渲染对特定测试没有用但断言 props 仍然有价值时,这很有用。
    【解决方案6】:

    给你。这支持 React 16+

    window.findReactComponent = function(el) {
      for (const key in el) {
        if (key.startsWith('__reactInternalInstance$')) {
          const fiberNode = el[key];
    
          return fiberNode && fiberNode.return && fiberNode.return.stateNode;
        }
      }
      return null;
    };

    【讨论】:

    • 这是最新的工作解决方案!但有一个警告 - 如果 el 不是组件的根元素,则不起作用。
    • 对我来说 stateNode 还不够,但这有效:el._debugOwner.stateNode.constructor.name
    • 这只会为我返回{containerInfo: body, pendingChildren: null, implementation: null}。此外,constructor.name 是 Object
    【解决方案7】:

    React 16+ 版本:

    如果你想要选择的 DOM 元素所属的最近的 React 组件实例,可以通过以下方式找到它(修改自 @Guan-Gui's 解决方案):

    window.getComponentFromElement = function(el) {
      for (const key in el) {
        if (key.startsWith('__reactInternalInstance$')) {
          const fiberNode = el[key];
          return fiberNode && fiberNode._debugOwner && fiberNode._debugOwner.stateNode;
        }
      }
      return null;
    };
    

    这里的技巧是使用_debugOwner 属性,它是对DOM 元素所属最近组件的FiberNode 的引用。

    警告:只有在开发模式下运行的组件才会有_debugOwner 属性。这在生产模式下不起作用。

    奖金

    我创建了这个方便的 sn-p,您可以在控制台中运行它,以便您可以单击任何元素并获取它所属的 React 组件实例。

    document.addEventListener('click', function(event) {
      const el = event.target;
      for (const key in el) {
        if (key.startsWith('__reactInternalInstance$')) {
          const fiberNode = el[key];
          const component = fiberNode && fiberNode._debugOwner;
          if (component) {
            console.log(component.type.displayName || component.type.name);
            window.$r = component.stateNode;
          }
          return;
        }
      }
    });
    

    【讨论】:

      【解决方案8】:

      安装 React devtools 并使用以下命令访问相应 dom 节点 ($0) 的 react 元素。

      对于 0.14.8

          var findReactNode = (node) =>Object.values(__REACT_DEVTOOLS_GLOBAL_HOOK__.helpers)[0]
      .getReactElementFromNative(node)
      ._currentElement;
             findReactNode($0);
      

      当然,它只是一个 hack..

      【讨论】:

        【解决方案9】:

        如果有人像我一样努力从 chrome 扩展访问 React 组件/属性,则上述所有解决方案都无法通过 chrome 扩展内容脚本工作。相反,您必须注入一个脚本标签并从那里运行您的代码。这是完整的解释: https://stackoverflow.com/a/9517879/2037323

        【讨论】:

        • 非常喜欢脱颖而出?
        猜你喜欢
        • 2014-08-19
        • 2016-02-21
        • 2015-06-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-02-21
        • 2022-12-05
        • 2021-10-21
        相关资源
        最近更新 更多