繁体   English   中英

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

[英]React - getting a component from a DOM element for debugging

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

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

我熟悉ReactChrome 调试扩展,但并非在所有浏览器中都可用。 结合 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 元素的第一个组件祖先。

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

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

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

功能组件

函数组件不像类那样具有“实例”,因此您不能仅仅修改FindReact函数来返回一个带有forceUpdatesetState等的对象,用于函数组件。

也就是说,您至少可以获取该路径的 React-fiber 节点,包含其道具、状态等。 为此,将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 节点访问任何反应组件

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

反应 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的最近组件的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($0);

当然,它只是一个黑客..

v15和v16与svg,html,注释,文本节点兼容

/* Node extends text, svg, html
 usage for node $0:
    $0.reactive // returns [node, parentNode, rootNode]
    $0.react.props // {any:'prop'}
    $0.react.setState(...) // update
 */
Object.defineProperties(Node.prototype, {
    _react: {writable:true, value:''}
    ,reactKey: {
        get: function(){
            let symbol = this._react;
            if(symbol){ return symbol; }
            // v15, v16 use a string as key, probably a real symbol in the future
            symbol = Object.keys(this).find(key => key.startsWith('__reactInternalInstance$'));
            return Node.prototype._react = symbol || '';
        }
    }
    // try to find the props/state/React-instance
    ,react: {
        get: function(){
            let react = this[ this.reactKey ] || null;
            let $0;
            if(react){
                $0 = react._currentElement;
                if($0){ // v15
                    if($0._owner){
                        return $0._owner._instance;
                    }else{
                        return $0;
                    };
                }
                $0 = react.return;
                if($0){ // v16
                    // develop mode only: return react._debugOwner.stateNode
                    // both develop and prod modes:
                    return $0.stateNode
                }
            }else if(this._reactRootContainer){
                // v16 _internalRoot === _internalRoot.current.stateNode
                return this._reactRootContainer._internalRoot;
            }
            return react;
        }
    }
    // make a list of self, ancestors that make up this branch of the tree
    ,reactive: {
        get: function(list=[]){
            let $0 = this;
            while($0 && !$0[ $0.reactKey ] && !$0._reactRootContainer ){
                $0 = $0.previousSibling;
            };
            if($0 && ($0[$0.reactKey] || $0._reactRootContainer)){
                list.push($0);
            };
            $0 = this;
            while($0 = $0.parentNode){
                if($0[ $0.reactKey ] || $0._reactRootContainer){
                    list.push($0);
                }
            };
            return list;
        }
    }
});

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM