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


73

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

先前已经在生产代码中使用此问题时询问过此问题。但是,我的重点是用于调试目的的开发版本。

我熟悉ReactChrome调试扩展,但是并非在所有浏览器中都可用。将DOM资源管理器和控制台结合使用,可以轻松地使用“ $ 0”快捷方式来访问有关突出显示的DOM元素的信息。

我想在调试控制台中编写如下代码:getComponentFromElement($ 0).props

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



编辑问题以解释与先前提出的问题的区别。
LodeRunner28年

@WiredPrairie这实际上有点不同-它更多是关于树过滤而不是简单地获取元素。
来自Qaribou,2015年

@ LodeRunner28检查我的答案
Shishir Arora

Answers:


17

我刚刚阅读了文档,并且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机制提供了我一直在寻找的东西。现在,在调试版本中,我可以提供一个可以在控制台中使用的全局函数,该函数可以搜索返回的组件列表,并将其与所选元素进行匹配。
LodeRunner28年

146

这是我使用的帮助器:(已更新为适用于React <16和16+)

function FindReact(dom, traverseUp = 0) {
    const key = Object.keys(dom).find(key=>key.startsWith("__reactInternalInstance$"));
    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)(默认情况下)将返回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;


2
render无法选择打补丁时(例如通过脚本注入增强页面),这就是解决方案。
dev

1
真棒的解决方案,帮助我非常多
亚瑟

2
(您可以FindReact($0)通过右键单击>检查来选择元素)
Bloke

1
惊人!完成别人说的是不可能的!
crazy2be

1
它从控制台为我工作,但是当我尝试从chrome扩展名使用它时,Object.keys对于相同的htmlnode为空。有什么建议?
拉扎·艾哈迈德

30

干得好。支持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
Christian

10

我写了这个小技巧,可以从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来设置react组件的props,我将如何反应来重绘组件?例如,说我有element.react.props.config.sunhat.enabled = false; 然后我该如何获取该元素以进行更新/渲染?我尝试了element.react.render(),但是似乎没有用,有什么想法吗?
user280109

2

这是我当前正在使用的一小段代码。

它适用于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)


2

我已经对@Venryx的答案进行了调整,使其略微适应了我的需求。此帮助器函数返回当前元素,而不是_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

@Venryx我们经常将_currentElement的props属性用于测试断言。当浅层渲染对于特定测试没有用,但是断言道具仍然有价值时,这很有用。
诺亚

2

安装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。


1

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属性,该属性是FiberNodeDOM元素所属的最近组件的引用。

注意:仅在开发模式下运行的组件才具有该_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;
    }
  }
});

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

1
您有一种非常不标准的代码格式化方法...大括号和关键字(或“(”和“)”)之间没有空格,参数中=之前和之后的空格,每个条目之前的逗号而不是之后的逗号,数组内的空格-literal []而不是object-literal {},使用$ 0作为一等变量名称,定义了许多原型扩展功能,等等。完全不是问题,只是觉得很有趣。^ _ ^
Venryx

1
很高兴我可以给@Venryx取乐,但是感谢您对答案的实质和实用性的反馈。如果语法令人分心,请随时进行编辑。我通常使用--fix整理工具来适应文化变量,这样我就不必花时间在细节上了-有一百种编写javascript的方法(显然只有一种是对的)。
jimmont

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.