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

React - getting a component from a DOM element for debugging

出于在控制台中进行调试的目的,React 中是否有任何可用的机制来使用 DOM 元素实例来获取后备 React 组件?

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

我熟悉 Chrome debugging extension for React,但并非所有浏览器都支持此功能。结合使用 DOM 资源管理器和控制台,可以轻松使用“$0”快捷方式访问有关突出显示的 DOM 元素的信息。

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

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

我刚看完文档,afaik none 对外暴露的API 会让你直接进去通过ID 找到一个React 组件。但是,您可以更新初始 React.render() 调用并将 return 值保留在某处,例如:

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

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

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

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

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

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

它适用于 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([=11=])

我写了这个小 hack 来允许从其 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

这是我使用的助手:(更新后适用于 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-节点的组件向上遍历的代码。 (如果没有这段代码,FindReact 函数在某些常见情况下会失败,如下所示)

绕过中间组件

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

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

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

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

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

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

函数组件

函数组件不像 类 那样具有“实例”,因此您不能只将 FindReact 函数修改为 return 具有 forceUpdate, setState, 等。对于函数组件。

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

我对@Venryx 的回答进行了调整,并使用了适合我需要的稍微调整的 ES6 版本。此辅助函数 returns 当前元素而不是 _owner._instance 属性.

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

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

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

奖金

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

0.14.8

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

当然,这只是一个 hack..

如果有人像我一样努力从 chrome 扩展访问 React component/properties,上述所有解决方案都无法从 chrome 扩展内容脚本中运行。相反,您必须从那里注入脚本标签和 运行 您的代码。这是完整的解释: