React - 从 DOM 元素获取组件以进行调试

React - getting a component from a DOM element for debugging

本文关键字:调试 组件 获取 DOM 元素 React      更新时间:2023-09-26

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

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

我熟悉 React 的 Chrome 调试扩展,但这并非在所有浏览器中都可用。结合 DOM 资源管理器和控制台,很容易使用"$0"快捷方式来访问有关突出显示的 DOM 元素的信息。

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

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

这是我

使用的助手:(更新为适用于 React <16 和 16+(

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-element 的第一个组件祖先。

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

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

有关遍历 React 组件树的更多详细信息,请参见此处。

功能组件

函数组件不像类那样有"实例",所以你不能只修改FindReact函数来返回一个对象,上面有forceUpdatesetState等。

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

你去吧。这支持 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;
};

我刚刚通读了文档,并且没有一个外部公开的 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

我写了这个小技巧,以便能够从其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

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

这是我目前正在使用的一个小片段。

它适用于 React 0.14.7。

代码要点

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)

我已经用适合我需求的稍微调整的 ES6 版本改编了@Venryx的答案。此帮助程序函数返回当前元素,而不是 _owner._instance 属性。

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

React 16+ 版本:

如果你想要所选 DOM 元素所属的最近的 React 组件实例,以下是找到它的方法(根据 @Guan-Gui 的解决方案修改(:

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 属性。这在生产模式下不起作用。

奖金

我创建了这个方便的代码片段,您可以在控制台中运行,以便您可以单击任何元素并获取它所属的 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;
    }
  }
});

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

对于 0.14.8

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

当然,这只是一个黑客。