From b6928936400dd23cba9fcff89b403b47eadd0e32 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 19 Apr 2016 16:19:31 +0100 Subject: [PATCH 01/20] Revert "Add ReactDebugInstanceMap" This reverts commit 575fb79162efa0f913c4fbd79960d4fc335e92cb. --- src/isomorphic/ReactDebugInstanceMap.js | 124 ------------- .../__tests__/ReactDebugInstanceMap-test.js | 173 ------------------ 2 files changed, 297 deletions(-) delete mode 100644 src/isomorphic/ReactDebugInstanceMap.js delete mode 100644 src/isomorphic/__tests__/ReactDebugInstanceMap-test.js diff --git a/src/isomorphic/ReactDebugInstanceMap.js b/src/isomorphic/ReactDebugInstanceMap.js deleted file mode 100644 index 50dddf4ad0e82..0000000000000 --- a/src/isomorphic/ReactDebugInstanceMap.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright 2016-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @providesModule ReactDebugInstanceMap - */ - -'use strict'; - -var warning = require('warning'); - -function checkValidInstance(internalInstance) { - if (!internalInstance) { - warning( - false, - 'There is an internal error in the React developer tools integration. ' + - 'Instead of an internal instance, received %s. ' + - 'Please report this as a bug in React.', - internalInstance - ); - return false; - } - var isValid = typeof internalInstance.mountComponent === 'function'; - warning( - isValid, - 'There is an internal error in the React developer tools integration. ' + - 'Instead of an internal instance, received an object with the following ' + - 'keys: %s. Please report this as a bug in React.', - Object.keys(internalInstance).join(', ') - ); - return isValid; -} - -var idCounter = 1; -var instancesByIDs = {}; -var instancesToIDs; - -function getIDForInstance(internalInstance) { - if (!instancesToIDs) { - instancesToIDs = new WeakMap(); - } - if (instancesToIDs.has(internalInstance)) { - return instancesToIDs.get(internalInstance); - } else { - var instanceID = (idCounter++).toString(); - instancesToIDs.set(internalInstance, instanceID); - return instanceID; - } -} - -function getInstanceByID(instanceID) { - return instancesByIDs[instanceID] || null; -} - -function isRegisteredInstance(internalInstance) { - var instanceID = getIDForInstance(internalInstance); - if (instanceID) { - return instancesByIDs.hasOwnProperty(instanceID); - } else { - return false; - } -} - -function registerInstance(internalInstance) { - var instanceID = getIDForInstance(internalInstance); - if (instanceID) { - instancesByIDs[instanceID] = internalInstance; - } -} - -function unregisterInstance(internalInstance) { - var instanceID = getIDForInstance(internalInstance); - if (instanceID) { - delete instancesByIDs[instanceID]; - } -} - -var ReactDebugInstanceMap = { - getIDForInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return null; - } - return getIDForInstance(internalInstance); - }, - getInstanceByID(instanceID) { - return getInstanceByID(instanceID); - }, - isRegisteredInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return false; - } - return isRegisteredInstance(internalInstance); - }, - registerInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return; - } - warning( - !isRegisteredInstance(internalInstance), - 'There is an internal error in the React developer tools integration. ' + - 'A registered instance should not be registered again. ' + - 'Please report this as a bug in React.' - ); - registerInstance(internalInstance); - }, - unregisterInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return; - } - warning( - isRegisteredInstance(internalInstance), - 'There is an internal error in the React developer tools integration. ' + - 'An unregistered instance should not be unregistered again. ' + - 'Please report this as a bug in React.' - ); - unregisterInstance(internalInstance); - }, -}; - -module.exports = ReactDebugInstanceMap; diff --git a/src/isomorphic/__tests__/ReactDebugInstanceMap-test.js b/src/isomorphic/__tests__/ReactDebugInstanceMap-test.js deleted file mode 100644 index d9a063e2c649f..0000000000000 --- a/src/isomorphic/__tests__/ReactDebugInstanceMap-test.js +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright 2016-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -describe('ReactDebugInstanceMap', function() { - var React; - var ReactDebugInstanceMap; - var ReactDOM; - - beforeEach(function() { - jest.resetModuleRegistry(); - React = require('React'); - ReactDebugInstanceMap = require('ReactDebugInstanceMap'); - ReactDOM = require('ReactDOM'); - }); - - function createStubInstance() { - return { mountComponent: () => {} }; - } - - it('should register and unregister instances', function() { - var inst1 = createStubInstance(); - var inst2 = createStubInstance(); - - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(false); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - - ReactDebugInstanceMap.registerInstance(inst1); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(true); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - - ReactDebugInstanceMap.registerInstance(inst2); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(true); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(true); - - ReactDebugInstanceMap.unregisterInstance(inst2); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(true); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - - ReactDebugInstanceMap.unregisterInstance(inst1); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(false); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - }); - - it('should assign stable IDs', function() { - var inst1 = createStubInstance(); - var inst2 = createStubInstance(); - - var inst1ID = ReactDebugInstanceMap.getIDForInstance(inst1); - var inst2ID = ReactDebugInstanceMap.getIDForInstance(inst2); - expect(typeof inst1ID).toBe('string'); - expect(typeof inst2ID).toBe('string'); - expect(inst1ID).not.toBe(inst2ID); - - ReactDebugInstanceMap.registerInstance(inst1); - ReactDebugInstanceMap.registerInstance(inst2); - expect(ReactDebugInstanceMap.getIDForInstance(inst1)).toBe(inst1ID); - expect(ReactDebugInstanceMap.getIDForInstance(inst2)).toBe(inst2ID); - - ReactDebugInstanceMap.unregisterInstance(inst1); - ReactDebugInstanceMap.unregisterInstance(inst2); - expect(ReactDebugInstanceMap.getIDForInstance(inst1)).toBe(inst1ID); - expect(ReactDebugInstanceMap.getIDForInstance(inst2)).toBe(inst2ID); - }); - - it('should retrieve registered instance by its ID', function() { - var inst1 = createStubInstance(); - var inst2 = createStubInstance(); - - var inst1ID = ReactDebugInstanceMap.getIDForInstance(inst1); - var inst2ID = ReactDebugInstanceMap.getIDForInstance(inst2); - expect(ReactDebugInstanceMap.getInstanceByID(inst1ID)).toBe(null); - expect(ReactDebugInstanceMap.getInstanceByID(inst2ID)).toBe(null); - - ReactDebugInstanceMap.registerInstance(inst1); - ReactDebugInstanceMap.registerInstance(inst2); - expect(ReactDebugInstanceMap.getInstanceByID(inst1ID)).toBe(inst1); - expect(ReactDebugInstanceMap.getInstanceByID(inst2ID)).toBe(inst2); - - ReactDebugInstanceMap.unregisterInstance(inst1); - ReactDebugInstanceMap.unregisterInstance(inst2); - expect(ReactDebugInstanceMap.getInstanceByID(inst1ID)).toBe(null); - expect(ReactDebugInstanceMap.getInstanceByID(inst2ID)).toBe(null); - }); - - it('should warn when registering an instance twice', function() { - spyOn(console, 'error'); - - var inst = createStubInstance(); - ReactDebugInstanceMap.registerInstance(inst); - expect(console.error.argsForCall.length).toBe(0); - - ReactDebugInstanceMap.registerInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - expect(console.error.argsForCall[0][0]).toContain( - 'There is an internal error in the React developer tools integration. ' + - 'A registered instance should not be registered again. ' + - 'Please report this as a bug in React.' - ); - - ReactDebugInstanceMap.unregisterInstance(inst); - ReactDebugInstanceMap.registerInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - }); - - it('should warn when unregistering an instance twice', function() { - spyOn(console, 'error'); - var inst = createStubInstance(); - - ReactDebugInstanceMap.unregisterInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - expect(console.error.argsForCall[0][0]).toContain( - 'There is an internal error in the React developer tools integration. ' + - 'An unregistered instance should not be unregistered again. ' + - 'Please report this as a bug in React.' - ); - - ReactDebugInstanceMap.registerInstance(inst); - ReactDebugInstanceMap.unregisterInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - - ReactDebugInstanceMap.unregisterInstance(inst); - expect(console.error.argsForCall.length).toBe(2); - expect(console.error.argsForCall[1][0]).toContain( - 'There is an internal error in the React developer tools integration. ' + - 'An unregistered instance should not be unregistered again. ' + - 'Please report this as a bug in React.' - ); - }); - - it('should warn about anything than is not an internal instance', function() { - class Foo extends React.Component { - render() { - return
; - } - } - - spyOn(console, 'error'); - var warningCount = 0; - var div = document.createElement('div'); - var publicInst = ReactDOM.render(, div); - - [false, null, undefined, {}, div, publicInst].forEach(falsyValue => { - ReactDebugInstanceMap.registerInstance(falsyValue); - warningCount++; - expect(ReactDebugInstanceMap.getIDForInstance(falsyValue)).toBe(null); - warningCount++; - expect(ReactDebugInstanceMap.isRegisteredInstance(falsyValue)).toBe(false); - warningCount++; - ReactDebugInstanceMap.unregisterInstance(falsyValue); - warningCount++; - }); - - expect(console.error.argsForCall.length).toBe(warningCount); - for (var i = 0; i < warningCount.length; i++) { - // Ideally we could check for the more detailed error message here - // but it depends on the input type and is meant for internal bugs - // anyway so I don't think it's worth complicating the test with it. - expect(console.error.argsForCall[i][0]).toContain( - 'There is an internal error in the React developer tools integration.' - ); - } - }); -}); From e9f8d57c7045f01a6a0083f7d6c9663fc63c90e2 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 19 Apr 2016 21:01:35 +0100 Subject: [PATCH 02/20] Add an initial implementation of ReactDebugTool events --- src/isomorphic/ReactDebugTool.js | 15 + .../__tests__/ReactDebugTool-test.js | 607 ++++++++++++++++++ src/renderers/dom/shared/ReactDOMComponent.js | 9 + .../reconciler/ReactCompositeComponent.js | 8 + .../shared/reconciler/ReactMultiChild.js | 9 + .../reconciler/instantiateReactComponent.js | 40 ++ 6 files changed, 688 insertions(+) create mode 100644 src/isomorphic/__tests__/ReactDebugTool-test.js diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 4d2c2a3536a5e..9fd6aa6e2da90 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -58,6 +58,21 @@ var ReactDebugTool = { onSetState() { emitEvent('onSetState'); }, + onSetIsComposite(debugID, isComposite) { + emitEvent('onSetIsComposite', debugID, isComposite); + }, + onSetDisplayName(debugID, displayName) { + emitEvent('onSetDisplayName', debugID, displayName); + }, + onSetChildren(debugID, childDebugIDs) { + emitEvent('onSetChildren', debugID, childDebugIDs); + }, + onSetOwner(debugID, ownerDebugID) { + emitEvent('onSetOwner', debugID, ownerDebugID); + }, + onSetText(debugID, text) { + emitEvent('onSetText', debugID, text); + }, onMountRootComponent(internalInstance) { emitEvent('onMountRootComponent', internalInstance); }, diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js new file mode 100644 index 0000000000000..103634b670a94 --- /dev/null +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -0,0 +1,607 @@ +/** + * Copyright 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +describe('ReactDebugTool', () => { + + + + + + + + + + + + + /* + TODO: + * Make sure update and unmount paths are tested + * Make sure server rendering works the same way + * Enable all existing tests + * Remove ReactInstanceMap usage + */ + + + + + + + + + + + + var React; + var ReactDebugTool; + var ReactDOM; + var ReactInstanceMap; + + var devtool; + + function createDevtool() { + var tree = {}; + + function updateTree(debugID, update) { + if (!tree[debugID]) { + tree[debugID] = {}; + } + update(tree[debugID]); + } + + var devtool = { + onSetIsComposite(debugID, isComposite) { + updateTree(debugID, item => item.isComposite = isComposite); + }, + onSetDisplayName(debugID, displayName) { + updateTree(debugID, item => item.displayName = displayName); + }, + onSetChildren(debugID, childDebugIDs) { + updateTree(debugID, item => item.childDebugIDs = childDebugIDs); + }, + onSetOwner(debugID, ownerDebugID) { + updateTree(debugID, item => item.ownerDebugID = ownerDebugID); + }, + onSetText(debugID, text) { + updateTree(debugID, item => item.text = text); + }, + getTree(debugID, includeOwner) { + var item = tree[debugID]; + var result = { + isComposite: item.isComposite, + displayName: item.displayName, + }; + result.children = item.childDebugIDs.map(childDebugID => + devtool.getTree(childDebugID, includeOwner) + ); + if (item.text != null) { + result.text = item.text; + } + if (includeOwner && item.ownerDebugID) { + result.ownerDisplayName = tree[item.ownerDebugID].displayName; + } + return result; + } + }; + + return devtool; + } + + beforeEach(() => { + jest.resetModuleRegistry(); + + React = require('React'); + ReactDebugTool = require('ReactDebugTool'); + ReactDOM = require('ReactDOM'); + ReactInstanceMap = require('ReactInstanceMap'); + + devtool = createDevtool(); + ReactDebugTool.addDevtool(devtool); + }); + + afterEach(() => { + ReactDebugTool.removeDevtool(devtool); + }); + + function assertTreeMatches(element, expectedTree, includeOwner) { + class Wrapper extends React.Component { + render() { + return element; + } + } + + var node = document.createElement('div'); + var rootPublicInstance = ReactDOM.render(, node); + var rootInstance = ReactInstanceMap.get(rootPublicInstance); + var actualTree = devtool.getTree( + rootInstance._renderedComponent._debugID, + includeOwner + ); + expect(actualTree).toEqual(expectedTree); + } + + it('uses displayName or Unknown for classic components', () => { + var Foo = React.createClass({ + render() { + return null; + }, + }); + Foo.displayName = 'Bar'; + var Baz = React.createClass({ + render() { + return null; + }, + }); + var Qux = React.createClass({ + render() { + return null; + }, + }); + delete Qux.displayName; + + var element =
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('uses displayName, name, or ReactComponent for modern components', () => { + class Foo extends React.Component { + render() { + return null; + } + } + Foo.displayName = 'Bar'; + class Baz extends React.Component { + render() { + return null; + } + } + class Qux extends React.Component { + render() { + return null; + } + } + delete Qux.name; + + var element =
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + // Note: Ideally fallback name should be consistent (e.g. "Unknown") + displayName: 'ReactComponent', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('uses displayName, name, or Object for factory components', () => { + function Foo() { + return { + render() { + return null; + }, + }; + } + Foo.displayName = 'Bar'; + function Baz() { + return { + render() { + return null; + }, + }; + } + function Qux() { + return { + render() { + return null; + }, + }; + } + delete Qux.name; + + var element =
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('uses displayName, name, or StatelessComponent for functional components', () => { + function Foo() { + return null; + } + Foo.displayName = 'Bar'; + function Baz() { + return null; + } + function Qux() { + return null; + } + delete Qux.name; + + var element =
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('reports a native tree correctly', () => { + var element = ( +
+

+ + Hi! + + Wow. +

+
+
+ ); + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'p', + children: [{ + isComposite: false, + displayName: 'span', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi!', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: 'Wow.', + children: [], + }], + }, { + isComposite: false, + displayName: 'hr', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('reports a simple tree with composites correctly', () => { + class Foo extends React.Component { + render() { + return
; + } + } + + var element = ; + var expectedTree = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('reports a tree with composites correctly', () => { + var Qux = React.createClass({ + render() { + return null; + }, + }); + function Foo() { + return { + render() { + return ; + }, + }; + } + function Bar({children}) { + return

{children}

; + } + class Baz extends React.Component { + render() { + return ( +
+ + + Hi, + Mom + + Click me. +
+ ); + } + } + + var element = ; + var expectedTree = { + isComposite: true, + displayName: 'Baz', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Qux', + children: [], + }], + }, { + isComposite: true, + displayName: 'Bar', + children: [{ + isComposite: false, + displayName: 'h1', + children: [{ + isComposite: false, + displayName: 'span', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi,', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: 'Mom', + children: [], + }], + }], + }, { + isComposite: false, + displayName: 'a', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Click me.', + children: [], + }], + }], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('ignores null children', () => { + class Foo extends React.Component { + render() { + return null; + } + } + var element = ; + var expectedTree = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + assertTreeMatches(element, expectedTree); + }); + + it('ignores false children', () => { + class Foo extends React.Component { + render() { + return false; + } + } + var element = ; + var expectedTree = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + assertTreeMatches(element, expectedTree); + }); + + it('reports text nodes as children', () => { + var element =
{'1'}{2}
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '1', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 2, + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('reports a single text node as a child', () => { + var element =
{'1'}
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '1', + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('reports a single number node as a child', () => { + var element =
{42}
; + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 42, + children: [], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('skips empty nodes for multiple children', () => { + function Foo() { + return
; + } + var element = ( +
+ {'hi'} + {false} + {42} + {null} + +
+ ); + var expectedTree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 42, + children: [], + }, { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + assertTreeMatches(element, expectedTree); + }); + + it('tracks owner correctly', () => { + class Foo extends React.Component { + render() { + return

Hi.

; + } + } + function Bar({children}) { + return
{children} Mom
; + } + + // Note that owner is not calculated for text nodes + // because they are not created from real elements. + var element =
; + var expectedTree = { + isComposite: false, + displayName: 'article', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + ownerDisplayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + ownerDisplayName: 'Bar', + children: [{ + isComposite: false, + displayName: 'h1', + ownerDisplayName: 'Foo', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: ' Mom', + children: [], + }], + }], + }], + }], + }; + assertTreeMatches(element, expectedTree, true); + }); +}); diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 5a7cf9ff11ce2..9a47a17637a04 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -32,6 +32,7 @@ var ReactDOMInput = require('ReactDOMInput'); var ReactDOMOption = require('ReactDOMOption'); var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChild = require('ReactMultiChild'); var ReactPerf = require('ReactPerf'); @@ -756,6 +757,14 @@ ReactDOMComponent.Mixin = { var childrenToUse = contentToUse != null ? null : props.children; if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node + if (__DEV__) { + var inlinedTextDebugID = this._debugID + '#text'; + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); + ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); + ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); + ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, contentToUse); + } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { var mountImages = this.mountChildren( diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index 907de98089ec0..ade929154820a 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -377,6 +377,14 @@ var ReactCompositeComponentMixin = { this._renderedComponent = this._instantiateReactComponent( renderedElement ); + if (__DEV__) { + if (this._renderedComponent._debugID) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + [this._renderedComponent._debugID] + ); + } + } var markup = ReactReconciler.mountComponent( this._renderedComponent, diff --git a/src/renderers/shared/reconciler/ReactMultiChild.js b/src/renderers/shared/reconciler/ReactMultiChild.js index 66f38613218be..6ca70e95a615d 100644 --- a/src/renderers/shared/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/reconciler/ReactMultiChild.js @@ -12,6 +12,7 @@ 'use strict'; var ReactComponentEnvironment = require('ReactComponentEnvironment'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var ReactCurrentOwner = require('ReactCurrentOwner'); @@ -214,6 +215,14 @@ var ReactMultiChild = { nestedChildren, transaction, context ); this._renderedChildren = children; + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + Object.keys(children).map(key => children[key]._debugID) + ); + } + var mountImages = []; var index = 0; for (var name in children) { diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index 796e037b9958b..e76d1c8bd00eb 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -14,6 +14,7 @@ var ReactCompositeComponent = require('ReactCompositeComponent'); var ReactEmptyComponent = require('ReactEmptyComponent'); var ReactNativeComponent = require('ReactNativeComponent'); +var ReactInstrumentation = require('ReactInstrumentation'); var invariant = require('invariant'); var warning = require('warning'); @@ -40,6 +41,21 @@ function getDeclarationErrorAddendum(owner) { return ''; } +function getDisplayName(instance) { + var element = instance._currentElement; + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else if (instance.getName) { + return instance.getName() || 'Unknown'; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } +} + /** * Check if the type reference is a known internal type. I.e. not a user * provided composite type. @@ -56,6 +72,8 @@ function isInternalComponentType(type) { ); } +var nextDebugID = 1; + /** * Given a ReactNode, create an instance that will actually be mounted. * @@ -66,7 +84,12 @@ function isInternalComponentType(type) { function instantiateReactComponent(node) { var instance; + var isEmpty = false; + var isText = false; + var isComposite = false; + if (node === null || node === false) { + isEmpty = true; instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; @@ -88,9 +111,11 @@ function instantiateReactComponent(node) { // representation, we can drop this code path. instance = new element.type(element); } else { + isComposite = true; instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { + isText = true; instance = ReactNativeComponent.createInstanceForText(node); } else { invariant( @@ -121,6 +146,21 @@ function instantiateReactComponent(node) { instance._warnedAboutRefsInRender = false; } + if (__DEV__) { + instance._debugID = isEmpty ? 0 : nextDebugID++; + var displayName = getDisplayName(instance); + ReactInstrumentation.debugTool.onSetIsComposite(instance._debugID, isComposite); + ReactInstrumentation.debugTool.onSetDisplayName(instance._debugID, displayName); + ReactInstrumentation.debugTool.onSetChildren(instance._debugID, []); + var owner = node && node._owner; + if (owner) { + ReactInstrumentation.debugTool.onSetOwner(instance._debugID, owner._debugID); + } + if (isText) { + ReactInstrumentation.debugTool.onSetText(instance._debugID, node); + } + } + // Internal instances should fully constructed at this point, so they should // not get any new fields added to them at this point. if (__DEV__) { From 7884606fdb4519cc0a062eb7cd98cfc889cfd8b7 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 20 Apr 2016 16:36:00 +0100 Subject: [PATCH 03/20] Handle updates to natives and composites --- .../__tests__/ReactDebugTool-test.js | 1951 +++++++++++++---- src/renderers/dom/shared/ReactDOMComponent.js | 13 +- .../dom/shared/ReactDOMTextComponent.js | 7 + .../reconciler/ReactCompositeComponent.js | 19 +- .../shared/reconciler/ReactMultiChild.js | 26 +- .../reconciler/instantiateReactComponent.js | 29 +- 6 files changed, 1593 insertions(+), 452 deletions(-) diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index 103634b670a94..b90c6bd35014f 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -12,36 +12,6 @@ 'use strict'; describe('ReactDebugTool', () => { - - - - - - - - - - - - - /* - TODO: - * Make sure update and unmount paths are tested - * Make sure server rendering works the same way - * Enable all existing tests - * Remove ReactInstanceMap usage - */ - - - - - - - - - - - var React; var ReactDebugTool; var ReactDOM; @@ -59,7 +29,25 @@ describe('ReactDebugTool', () => { update(tree[debugID]); } - var devtool = { + function getTree(debugID, includeOwner) { + var item = tree[debugID]; + var result = { + isComposite: item.isComposite, + displayName: item.displayName, + }; + result.children = item.childDebugIDs.map(childDebugID => + getTree(childDebugID, includeOwner) + ); + if (item.text != null) { + result.text = item.text; + } + if (includeOwner && item.ownerDebugID) { + result.ownerDisplayName = tree[item.ownerDebugID].displayName; + } + return result; + } + + return { onSetIsComposite(debugID, isComposite) { updateTree(debugID, item => item.isComposite = isComposite); }, @@ -75,26 +63,10 @@ describe('ReactDebugTool', () => { onSetText(debugID, text) { updateTree(debugID, item => item.text = text); }, - getTree(debugID, includeOwner) { - var item = tree[debugID]; - var result = { - isComposite: item.isComposite, - displayName: item.displayName, - }; - result.children = item.childDebugIDs.map(childDebugID => - devtool.getTree(childDebugID, includeOwner) - ); - if (item.text != null) { - result.text = item.text; - } - if (includeOwner && item.ownerDebugID) { - result.ownerDisplayName = tree[item.ownerDebugID].displayName; - } - return result; - } + getTree(rootDebugID, includeOwner) { + return getTree(rootDebugID, includeOwner); + }, }; - - return devtool; } beforeEach(() => { @@ -113,446 +85,1581 @@ describe('ReactDebugTool', () => { ReactDebugTool.removeDevtool(devtool); }); - function assertTreeMatches(element, expectedTree, includeOwner) { + function assertTreeMatches(pairs, includeOwner) { + if (!Array.isArray(pairs[0])) { + pairs = [pairs]; + } + + var currentElement; class Wrapper extends React.Component { render() { - return element; + return currentElement; } } var node = document.createElement('div'); - var rootPublicInstance = ReactDOM.render(, node); - var rootInstance = ReactInstanceMap.get(rootPublicInstance); - var actualTree = devtool.getTree( - rootInstance._renderedComponent._debugID, - includeOwner - ); - expect(actualTree).toEqual(expectedTree); - } - - it('uses displayName or Unknown for classic components', () => { - var Foo = React.createClass({ - render() { - return null; - }, - }); - Foo.displayName = 'Bar'; - var Baz = React.createClass({ - render() { - return null; - }, - }); - var Qux = React.createClass({ - render() { - return null; - }, - }); - delete Qux.displayName; - - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - displayName: 'Unknown', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - it('uses displayName, name, or ReactComponent for modern components', () => { - class Foo extends React.Component { - render() { - return null; - } - } - Foo.displayName = 'Bar'; - class Baz extends React.Component { - render() { - return null; - } - } - class Qux extends React.Component { - render() { - return null; - } - } - delete Qux.name; + pairs.forEach(([element, expectedTree]) => { + currentElement = element; - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - // Note: Ideally fallback name should be consistent (e.g. "Unknown") - displayName: 'ReactComponent', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + var rootPublicInstance = ReactDOM.render(, node); + var rootInstance = ReactInstanceMap.get(rootPublicInstance); + var actualTree = devtool.getTree( + rootInstance._renderedComponent._debugID, + includeOwner + ); + expect(actualTree).toEqual(expectedTree); + }); + } - it('uses displayName, name, or Object for factory components', () => { - function Foo() { - return { + describe('mount', () => { + it('uses displayName or Unknown for classic components', () => { + var Foo = React.createClass({ render() { return null; }, - }; - } - Foo.displayName = 'Bar'; - function Baz() { - return { + }); + Foo.displayName = 'Bar'; + var Baz = React.createClass({ render() { return null; }, - }; - } - function Qux() { - return { + }); + var Qux = React.createClass({ render() { return null; }, - }; - } - delete Qux.name; + }); + delete Qux.displayName; - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - displayName: 'Unknown', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('uses displayName, name, or StatelessComponent for functional components', () => { - function Foo() { - return null; - } - Foo.displayName = 'Bar'; - function Baz() { - return null; - } - function Qux() { - return null; - } - delete Qux.name; + var element =
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - displayName: 'Unknown', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + it('uses displayName, name, or ReactComponent for modern components', () => { + class Foo extends React.Component { + render() { + return null; + } + } + Foo.displayName = 'Bar'; + class Baz extends React.Component { + render() { + return null; + } + } + class Qux extends React.Component { + render() { + return null; + } + } + delete Qux.name; - it('reports a native tree correctly', () => { - var element = ( -
-

- - Hi! - - Wow. -

-
-
- ); - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ + var element =
; + var tree = { isComposite: false, - displayName: 'p', + displayName: 'div', children: [{ - isComposite: false, - displayName: 'span', - children: [{ - isComposite: false, - displayName: '#text', - text: 'Hi!', - children: [], - }], + isComposite: true, + displayName: 'Bar', + children: [], }, { - isComposite: false, - displayName: '#text', - text: 'Wow.', + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + // Note: Ideally fallback name should be consistent (e.g. "Unknown") + displayName: 'ReactComponent', children: [], }], - }, { - isComposite: false, - displayName: 'hr', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + }; + assertTreeMatches([element, tree]); + }); - it('reports a simple tree with composites correctly', () => { - class Foo extends React.Component { - render() { - return
; + it('uses displayName, name, or Object for factory components', () => { + function Foo() { + return { + render() { + return null; + }, + }; } - } + Foo.displayName = 'Bar'; + function Baz() { + return { + render() { + return null; + }, + }; + } + function Qux() { + return { + render() { + return null; + }, + }; + } + delete Qux.name; - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Foo', - children: [{ + var element =
; + var tree = { isComposite: false, displayName: 'div', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); - it('reports a tree with composites correctly', () => { - var Qux = React.createClass({ - render() { + it('uses displayName, name, or StatelessComponent for functional components', () => { + function Foo() { return null; - }, - }); - function Foo() { - return { - render() { - return ; - }, - }; - } - function Bar({children}) { - return

{children}

; - } - class Baz extends React.Component { - render() { - return ( -
- - - Hi, - Mom - - Click me. -
- ); } - } + Foo.displayName = 'Bar'; + function Baz() { + return null; + } + function Qux() { + return null; + } + delete Qux.name; - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Baz', - children: [{ + var element =
; + var tree = { isComposite: false, displayName: 'div', children: [{ isComposite: true, - displayName: 'Foo', - children: [{ - isComposite: true, - displayName: 'Qux', - children: [], - }], + displayName: 'Bar', + children: [], }, { isComposite: true, - displayName: 'Bar', + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a native tree correctly', () => { + var element = ( +
+

+ + Hi! + + Wow. +

+
+
+ ); + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'p', children: [{ isComposite: false, - displayName: 'h1', + displayName: 'span', children: [{ - isComposite: false, - displayName: 'span', - children: [{ - isComposite: false, - displayName: '#text', - text: 'Hi,', - children: [], - }], - }, { isComposite: false, displayName: '#text', - text: 'Mom', + text: 'Hi!', children: [], }], - }], - }, { - isComposite: false, - displayName: 'a', - children: [{ + }, { isComposite: false, displayName: '#text', - text: 'Click me.', + text: 'Wow.', children: [], }], - }], - }], - }; - assertTreeMatches(element, expectedTree); - }); + }, { + isComposite: false, + displayName: 'hr', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); - it('ignores null children', () => { - class Foo extends React.Component { - render() { - return null; + it('reports a simple tree with composites correctly', () => { + class Foo extends React.Component { + render() { + return
; + } } - } - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Foo', - children: [], - }; - assertTreeMatches(element, expectedTree); - }); - it('ignores false children', () => { - class Foo extends React.Component { - render() { - return false; + var element = ; + var tree = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a tree with composites correctly', () => { + var Qux = React.createClass({ + render() { + return null; + }, + }); + function Foo() { + return { + render() { + return ; + }, + }; + } + function Bar({children}) { + return

{children}

; + } + class Baz extends React.Component { + render() { + return ( +
+ + + Hi, + Mom + + Click me. +
+ ); + } } - } - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Foo', - children: [], - }; - assertTreeMatches(element, expectedTree); - }); - it('reports text nodes as children', () => { - var element =
{'1'}{2}
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: false, - displayName: '#text', - text: '1', + var element = ; + var tree = { + isComposite: true, + displayName: 'Baz', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Qux', + children: [], + }], + }, { + isComposite: true, + displayName: 'Bar', + children: [{ + isComposite: false, + displayName: 'h1', + children: [{ + isComposite: false, + displayName: 'span', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi,', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: 'Mom', + children: [], + }], + }], + }, { + isComposite: false, + displayName: 'a', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Click me.', + children: [], + }], + }], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('ignores null children', () => { + class Foo extends React.Component { + render() { + return null; + } + } + var element = ; + var tree = { + isComposite: true, + displayName: 'Foo', children: [], - }, { - isComposite: false, - displayName: '#text', - text: 2, + }; + assertTreeMatches([element, tree]); + }); + + it('ignores false children', () => { + class Foo extends React.Component { + render() { + return false; + } + } + var element = ; + var tree = { + isComposite: true, + displayName: 'Foo', children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + }; + assertTreeMatches([element, tree]); + }); - it('reports a single text node as a child', () => { - var element =
{'1'}
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ + it('reports text nodes as children', () => { + var element =
{'1'}{2}
; + var tree = { isComposite: false, - displayName: '#text', - text: '1', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '1', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: '2', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); - it('reports a single number node as a child', () => { - var element =
{42}
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ + it('reports a single text node as a child', () => { + var element =
{'1'}
; + var tree = { isComposite: false, - displayName: '#text', - text: 42, - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '1', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); - it('skips empty nodes for multiple children', () => { - function Foo() { - return
; - } - var element = ( -
- {'hi'} - {false} - {42} - {null} - -
- ); - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ + it('reports a single number node as a child', () => { + var element =
{42}
; + var tree = { isComposite: false, - displayName: '#text', - text: 'hi', - children: [], - }, { + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '42', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a zero as a child', () => { + var element =
{0}
; + var tree = { isComposite: false, - displayName: '#text', - text: 42, - children: [], - }, { - isComposite: true, - displayName: 'Foo', + displayName: 'div', children: [{ isComposite: false, - displayName: 'div', + displayName: '#text', + text: '0', children: [], }], - }], - }; - assertTreeMatches(element, expectedTree); + }; + assertTreeMatches([element, tree]); + }); + + it('skips empty nodes for multiple children', () => { + function Foo() { + return
; + } + var element = ( +
+ {'hi'} + {false} + {42} + {null} + +
+ ); + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: '42', + children: [], + }, { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + assertTreeMatches([element, tree]); + }); + }); + + describe('update', () => { + describe('native component', () => { + it('updates text of a single text child', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
Bye.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to a single text child', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
Hi.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a single text child to no children', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to multiple text children', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
{'Hi.'}{'Bye.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from multiple text children to no children', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from one text child to multiple text children', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
{'Hi.'}{'Bye.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from multiple text children to one text child', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
Hi.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates text nodes when reordering', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
{'Bye.'}{'Hi.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates native nodes when reordering with keys', () => { + var elementBefore = ( +
+
Hi.
+
Bye.
+
+ ); + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }], + }; + + var elementAfter = ( +
+
Bye.
+
Hi.
+
+ ); + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates native nodes when reordering without keys', () => { + var elementBefore = ( +
+
Hi.
+
Bye.
+
+ ); + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }], + }; + + var elementAfter = ( +
+
Bye.
+
Hi.
+
+ ); + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates a single composite child of a different type', () => { + function Foo() { + return null; + } + + function Bar() { + return null; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates a single composite child of the same type', () => { + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'span', + children: [], + }], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to a single composite child', () => { + function Foo() { + return null; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a single composite child to no children', () => { + function Foo() { + return null; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates mixed children', () => { + function Foo() { + return
; + } + var element1 = ( +
+ {'hi'} + {false} + {42} + {null} + +
+ ); + var tree1 = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: '42', + children: [], + }, { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + + var element2 = ( +
+ + {false} + {'hi'} + {null} +
+ ); + var tree2 = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }], + }; + + var element3 = ( +
+ +
+ ); + var tree3 = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + + assertTreeMatches([ + [element1, tree1], + [element2, tree2], + [element3, tree3], + ]); + }); + }); + + describe('functional component', () => { + it('updates with a native child', () => { + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'span', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a native child', () => { + function Foo({ children }) { + return children; + } + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to null', () => { + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to a composite child', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to a native child', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a composite child', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to null', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + }); + + describe('class component', () => { + it('updates with a native child', () => { + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'span', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a native child', () => { + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to null', () => { + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to a composite child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to a native child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a composite child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to null', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + }); }); it('tracks owner correctly', () => { @@ -568,7 +1675,7 @@ describe('ReactDebugTool', () => { // Note that owner is not calculated for text nodes // because they are not created from real elements. var element =
; - var expectedTree = { + var tree = { isComposite: false, displayName: 'article', children: [{ @@ -602,6 +1709,6 @@ describe('ReactDebugTool', () => { }], }], }; - assertTreeMatches(element, expectedTree, true); + assertTreeMatches([element, tree], true); }); }); diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 9a47a17637a04..681efb7cf2b8a 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -763,7 +763,7 @@ ReactDOMComponent.Mixin = { ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); - ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, contentToUse); + ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + contentToUse); } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { @@ -1012,11 +1012,22 @@ ReactDOMComponent.Mixin = { this.updateChildren(null, transaction, context); } else if (lastHasContentOrHtml && !nextHasContentOrHtml) { this.updateTextContent(''); + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); + } } if (nextContent != null) { if (lastContent !== nextContent) { this.updateTextContent('' + nextContent); + if (__DEV__) { + var inlinedTextDebugID = this._debugID + '#text'; + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); + ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); + ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); + ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + nextContent); + } } } else if (nextHtml != null) { if (lastHtml !== nextHtml) { diff --git a/src/renderers/dom/shared/ReactDOMTextComponent.js b/src/renderers/dom/shared/ReactDOMTextComponent.js index 9a69c7f97762a..5041706287355 100644 --- a/src/renderers/dom/shared/ReactDOMTextComponent.js +++ b/src/renderers/dom/shared/ReactDOMTextComponent.js @@ -14,6 +14,7 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var DOMLazyTree = require('DOMLazyTree'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); @@ -67,6 +68,8 @@ Object.assign(ReactDOMTextComponent.prototype, { context ) { if (__DEV__) { + ReactInstrumentation.debugTool.onSetText(this._debugID, this._stringText); + var parentInfo; if (nativeParent != null) { parentInfo = nativeParent._ancestorInfo; @@ -140,6 +143,10 @@ Object.assign(ReactDOMTextComponent.prototype, { commentNodes[1], nextStringText ); + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetText(this._debugID, nextStringText); + } } } }, diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index ade929154820a..103e21d89e4b0 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -378,12 +378,12 @@ var ReactCompositeComponentMixin = { renderedElement ); if (__DEV__) { - if (this._renderedComponent._debugID) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedNodeType === ReactNodeTypes.EMPTY ? + [] : [this._renderedComponent._debugID] - ); - } + ); } var markup = ReactReconciler.mountComponent( @@ -861,6 +861,15 @@ var ReactCompositeComponentMixin = { this._renderedComponent = this._instantiateReactComponent( nextRenderedElement ); + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedNodeType === ReactNodeTypes.EMPTY ? + [] : + [this._renderedComponent._debugID] + ); + } + var nextMarkup = ReactReconciler.mountComponent( this._renderedComponent, transaction, diff --git a/src/renderers/shared/reconciler/ReactMultiChild.js b/src/renderers/shared/reconciler/ReactMultiChild.js index 6ca70e95a615d..2a7ccb70e36e8 100644 --- a/src/renderers/shared/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/reconciler/ReactMultiChild.js @@ -216,13 +216,6 @@ var ReactMultiChild = { ); this._renderedChildren = children; - if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - Object.keys(children).map(key => children[key]._debugID) - ); - } - var mountImages = []; var index = 0; for (var name in children) { @@ -239,6 +232,16 @@ var ReactMultiChild = { mountImages.push(mountImage); } } + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + children ? + Object.keys(children).map(key => children[key]._debugID) : + [] + ); + } + return mountImages; }, @@ -366,6 +369,15 @@ var ReactMultiChild = { processQueue(this, updates); } this._renderedChildren = nextChildren; + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + nextChildren ? + Object.keys(nextChildren).map(key => nextChildren[key]._debugID) : + [] + ); + } }, /** diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index e76d1c8bd00eb..f5cf2d95d56bd 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -42,18 +42,18 @@ function getDeclarationErrorAddendum(owner) { } function getDisplayName(instance) { - var element = instance._currentElement; - if (element == null) { - return '#empty'; - } else if (typeof element === 'string' || typeof element === 'number') { - return '#text'; - } else if (typeof element.type === 'string') { - return element.type; - } else if (instance.getName) { - return instance.getName() || 'Unknown'; - } else { - return element.type.displayName || element.type.name || 'Unknown'; - } + var element = instance._currentElement; + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else if (instance.getName) { + return instance.getName() || 'Unknown'; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } } /** @@ -85,7 +85,6 @@ function instantiateReactComponent(node) { var instance; var isEmpty = false; - var isText = false; var isComposite = false; if (node === null || node === false) { @@ -115,7 +114,6 @@ function instantiateReactComponent(node) { instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { - isText = true; instance = ReactNativeComponent.createInstanceForText(node); } else { invariant( @@ -156,9 +154,6 @@ function instantiateReactComponent(node) { if (owner) { ReactInstrumentation.debugTool.onSetOwner(instance._debugID, owner._debugID); } - if (isText) { - ReactInstrumentation.debugTool.onSetText(instance._debugID, node); - } } // Internal instances should fully constructed at this point, so they should From 804c680f061c7cf055bf56c12a995308dc150f4b Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 20 Apr 2016 18:01:52 +0100 Subject: [PATCH 04/20] Report dangerouslySetInnerHTML as no children --- .../__tests__/ReactDebugTool-test.js | 166 ++++++++++++++++++ src/renderers/dom/shared/ReactDOMComponent.js | 3 + 2 files changed, 169 insertions(+) diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index b90c6bd35014f..a7442e5b88bb6 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -552,6 +552,16 @@ describe('ReactDebugTool', () => { }; assertTreeMatches([element, tree]); }); + + it('reports html content as no children', () => { + var element =
; + var tree = { + isComposite: false, + displayName: 'div', + children: [], + }; + assertTreeMatches([element, tree]); + }); }); describe('update', () => { @@ -639,6 +649,58 @@ describe('ReactDebugTool', () => { ]); }); + it('updates from html content to a single text child', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
Hi.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a single text child to html content', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + it('updates from no children to multiple text children', () => { var elementBefore =
; var treeBefore = { @@ -701,6 +763,110 @@ describe('ReactDebugTool', () => { ]); }); + it('updates from html content to multiple text children', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
{'Hi.'}{'Bye.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from multiple text children to html content', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from html content to no children', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to html content', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + it('updates from one text child to multiple text children', () => { var elementBefore =
Hi.
; var treeBefore = { diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 681efb7cf2b8a..2514cd7c7057c 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -1033,6 +1033,9 @@ ReactDOMComponent.Mixin = { if (lastHtml !== nextHtml) { this.updateMarkup('' + nextHtml); } + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); + } } else if (nextChildren != null) { this.updateChildren(nextChildren, transaction, context); } From 1587abf6f06f4c3ab164eb69c256f3f8ceb3c526 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 20 Apr 2016 18:12:40 +0100 Subject: [PATCH 05/20] Don't report children for empty and text nodes --- .../__tests__/ReactDebugTool-test.js | 57 ++----------------- src/renderers/dom/shared/ReactDOMComponent.js | 2 - .../reconciler/instantiateReactComponent.js | 7 ++- 3 files changed, 11 insertions(+), 55 deletions(-) diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index a7442e5b88bb6..faff237152cc8 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -35,9 +35,11 @@ describe('ReactDebugTool', () => { isComposite: item.isComposite, displayName: item.displayName, }; - result.children = item.childDebugIDs.map(childDebugID => - getTree(childDebugID, includeOwner) - ); + if (item.childDebugIDs) { + result.children = item.childDebugIDs.map(childDebugID => + getTree(childDebugID, includeOwner) + ); + } if (item.text != null) { result.text = item.text; } @@ -299,13 +301,11 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi!', - children: [], }], }, { isComposite: false, displayName: '#text', text: 'Wow.', - children: [], }], }, { isComposite: false, @@ -395,13 +395,11 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi,', - children: [], }], }, { isComposite: false, displayName: '#text', text: 'Mom', - children: [], }], }], }, { @@ -411,7 +409,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Click me.', - children: [], }], }], }], @@ -458,12 +455,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: '1', - children: [], }, { isComposite: false, displayName: '#text', text: '2', - children: [], }], }; assertTreeMatches([element, tree]); @@ -478,7 +473,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: '1', - children: [], }], }; assertTreeMatches([element, tree]); @@ -493,7 +487,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: '42', - children: [], }], }; assertTreeMatches([element, tree]); @@ -508,7 +501,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: '0', - children: [], }], }; assertTreeMatches([element, tree]); @@ -534,12 +526,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'hi', - children: [], }, { isComposite: false, displayName: '#text', text: '42', - children: [], }, { isComposite: true, displayName: 'Foo', @@ -575,7 +565,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; @@ -587,7 +576,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -613,7 +601,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; @@ -632,7 +619,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; @@ -665,7 +651,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; @@ -684,7 +669,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; @@ -717,12 +701,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -741,12 +723,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -779,12 +759,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -803,12 +781,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -876,7 +852,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; @@ -888,12 +863,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -912,12 +885,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -929,7 +900,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; assertTreeMatches([ @@ -947,12 +917,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }; @@ -964,12 +932,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }, { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }; assertTreeMatches([ @@ -995,7 +961,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }, { isComposite: false, @@ -1004,7 +969,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }], }; @@ -1025,7 +989,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }, { isComposite: false, @@ -1034,7 +997,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }], }; @@ -1062,7 +1024,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }, { isComposite: false, @@ -1071,7 +1032,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }], }; @@ -1092,7 +1052,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Bye.', - children: [], }], }, { isComposite: false, @@ -1101,7 +1060,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }], }; @@ -1268,12 +1226,10 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'hi', - children: [], }, { isComposite: false, displayName: '#text', text: '42', - children: [], }, { isComposite: true, displayName: 'Foo', @@ -1308,7 +1264,6 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'hi', - children: [], }], }; @@ -1863,13 +1818,11 @@ describe('ReactDebugTool', () => { isComposite: false, displayName: '#text', text: 'Hi.', - children: [], }], }, { isComposite: false, displayName: '#text', text: ' Mom', - children: [], }], }], }], diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 2514cd7c7057c..5f944679112bb 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -762,7 +762,6 @@ ReactDOMComponent.Mixin = { ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); - ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + contentToUse); } DOMLazyTree.queueText(lazyTree, contentToUse); @@ -1025,7 +1024,6 @@ ReactDOMComponent.Mixin = { ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); - ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + nextContent); } } diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index f5cf2d95d56bd..07f0766d65191 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -85,6 +85,7 @@ function instantiateReactComponent(node) { var instance; var isEmpty = false; + var isNative = false; var isComposite = false; if (node === null || node === false) { @@ -103,8 +104,10 @@ function instantiateReactComponent(node) { // Special case string values if (typeof element.type === 'string') { + isNative = true; instance = ReactNativeComponent.createInternalComponent(element); } else if (isInternalComponentType(element.type)) { + isComposite = true; // This is temporarily available for custom components that are not string // representations. I.e. ART. Once those are updated to use the string // representation, we can drop this code path. @@ -149,7 +152,9 @@ function instantiateReactComponent(node) { var displayName = getDisplayName(instance); ReactInstrumentation.debugTool.onSetIsComposite(instance._debugID, isComposite); ReactInstrumentation.debugTool.onSetDisplayName(instance._debugID, displayName); - ReactInstrumentation.debugTool.onSetChildren(instance._debugID, []); + if (isNative || isComposite) { + ReactInstrumentation.debugTool.onSetChildren(instance._debugID, []); + } var owner = node && node._owner; if (owner) { ReactInstrumentation.debugTool.onSetOwner(instance._debugID, owner._debugID); From 367594a2139b08e56dfcb7554d76ece0f9282a6c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 20 Apr 2016 18:20:14 +0100 Subject: [PATCH 06/20] Enforce that info about children is available by the time onSetChildren() fires --- src/isomorphic/__tests__/ReactDebugTool-test.js | 7 +++++++ src/renderers/dom/shared/ReactDOMComponent.js | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index faff237152cc8..85d6d64b02b38 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -57,6 +57,13 @@ describe('ReactDebugTool', () => { updateTree(debugID, item => item.displayName = displayName); }, onSetChildren(debugID, childDebugIDs) { + childDebugIDs.forEach(childDebugID => { + var childItem = tree[childDebugID]; + expect(childItem).toBeDefined(); + expect(childItem.isComposite).toBeDefined(); + expect(childItem.displayName).toBeDefined(); + expect(childItem.childDebugIDs || childItem.text).toBeDefined(); + }); updateTree(debugID, item => item.childDebugIDs = childDebugIDs); }, onSetOwner(debugID, ownerDebugID) { diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 5f944679112bb..50db5019d4746 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -759,10 +759,10 @@ ReactDOMComponent.Mixin = { // TODO: Validate that text is allowed as a child of this node if (__DEV__) { var inlinedTextDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + contentToUse); + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { @@ -1021,10 +1021,10 @@ ReactDOMComponent.Mixin = { this.updateTextContent('' + nextContent); if (__DEV__) { var inlinedTextDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + nextContent); + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); } } } else if (nextHtml != null) { From af3603ea2f7fb3cad5632a4e6ae9b4fcc579f91e Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 20 Apr 2016 19:01:24 +0100 Subject: [PATCH 07/20] Devtools should be able to clean up the tree on client rendering --- src/isomorphic/ReactDebugTool.js | 16 ++-- .../__tests__/ReactDebugTool-test.js | 75 +++++++++++++------ src/renderers/dom/client/ReactMount.js | 2 +- src/renderers/dom/shared/ReactDOMComponent.js | 33 +++++--- .../shared/reconciler/ReactReconciler.js | 8 +- 5 files changed, 89 insertions(+), 45 deletions(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 9fd6aa6e2da90..92fbcc0bcea65 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -73,17 +73,17 @@ var ReactDebugTool = { onSetText(debugID, text) { emitEvent('onSetText', debugID, text); }, - onMountRootComponent(internalInstance) { - emitEvent('onMountRootComponent', internalInstance); + onMountRootComponent(debugID) { + emitEvent('onMountRootComponent', debugID); }, - onMountComponent(internalInstance) { - emitEvent('onMountComponent', internalInstance); + onMountComponent(debugID) { + emitEvent('onMountComponent', debugID); }, - onUpdateComponent(internalInstance) { - emitEvent('onUpdateComponent', internalInstance); + onUpdateComponent(debugID) { + emitEvent('onUpdateComponent', debugID); }, - onUnmountComponent(internalInstance) { - emitEvent('onUnmountComponent', internalInstance); + onUnmountComponent(debugID) { + emitEvent('onUnmountComponent', debugID); }, }; diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index 85d6d64b02b38..b664ecfd2ab27 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -22,22 +22,22 @@ describe('ReactDebugTool', () => { function createDevtool() { var tree = {}; - function updateTree(debugID, update) { - if (!tree[debugID]) { - tree[debugID] = {}; + function updateTree(id, update) { + if (!tree[id]) { + tree[id] = {}; } - update(tree[debugID]); + update(tree[id]); } - function getTree(debugID, includeOwner) { - var item = tree[debugID]; + function getTree(id, includeOwner) { + var item = tree[id]; var result = { isComposite: item.isComposite, displayName: item.displayName, }; - if (item.childDebugIDs) { - result.children = item.childDebugIDs.map(childDebugID => - getTree(childDebugID, includeOwner) + if (item.childIDs) { + result.children = item.childIDs.map(childID => + getTree(childID, includeOwner) ); } if (item.text != null) { @@ -49,29 +49,57 @@ describe('ReactDebugTool', () => { return result; } + function purgeTree(id) { + var item = tree[id]; + if (!item) { + return; + } + + var {childIDs} = item; + delete tree[id]; + + if (childIDs) { + childIDs.forEach(purgeTree); + } + } + return { - onSetIsComposite(debugID, isComposite) { - updateTree(debugID, item => item.isComposite = isComposite); + onSetIsComposite(id, isComposite) { + updateTree(id, item => item.isComposite = isComposite); }, - onSetDisplayName(debugID, displayName) { - updateTree(debugID, item => item.displayName = displayName); + + onSetDisplayName(id, displayName) { + updateTree(id, item => item.displayName = displayName); }, - onSetChildren(debugID, childDebugIDs) { - childDebugIDs.forEach(childDebugID => { - var childItem = tree[childDebugID]; + + onSetChildren(id, childIDs) { + childIDs.forEach(childID => { + var childItem = tree[childID]; expect(childItem).toBeDefined(); expect(childItem.isComposite).toBeDefined(); expect(childItem.displayName).toBeDefined(); - expect(childItem.childDebugIDs || childItem.text).toBeDefined(); + expect(childItem.childIDs || childItem.text).toBeDefined(); }); - updateTree(debugID, item => item.childDebugIDs = childDebugIDs); + + updateTree(id, item => item.childIDs = childIDs); }, - onSetOwner(debugID, ownerDebugID) { - updateTree(debugID, item => item.ownerDebugID = ownerDebugID); + + onSetOwner(id, ownerDebugID) { + updateTree(id, item => item.ownerDebugID = ownerDebugID); + }, + + onSetText(id, text) { + updateTree(id, item => item.text = text); + }, + + onUnmountComponent(id) { + purgeTree(id); }, - onSetText(debugID, text) { - updateTree(debugID, item => item.text = text); + + getRegisteredDebugIDs() { + return Object.keys(tree); }, + getTree(rootDebugID, includeOwner) { return getTree(rootDebugID, includeOwner); }, @@ -119,6 +147,9 @@ describe('ReactDebugTool', () => { ); expect(actualTree).toEqual(expectedTree); }); + + ReactDOM.unmountComponentAtNode(node); + expect(devtool.getRegisteredDebugIDs()).toEqual([]); } describe('mount', () => { diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 7f6f8cac6ff5f..8f62d5b930921 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -349,7 +349,7 @@ var ReactMount = { instancesByReactRootID[wrapperID] = componentInstance; if (__DEV__) { - ReactInstrumentation.debugTool.onMountRootComponent(componentInstance); + ReactInstrumentation.debugTool.onMountRootComponent(componentInstance._debugID); } return componentInstance; diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 50db5019d4746..f6d4823a1c749 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -453,6 +453,7 @@ function ReactDOMComponent(element) { this._flags = 0; if (__DEV__) { this._ancestorInfo = null; + this._contentDebugID = null; } } @@ -758,11 +759,11 @@ ReactDOMComponent.Mixin = { if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node if (__DEV__) { - var inlinedTextDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); - ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); - ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + contentToUse); - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); + this._contentDebugID = this._debugID + '#text'; + ReactInstrumentation.debugTool.onSetIsComposite(this._contentDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(this._contentDebugID, '#text'); + ReactInstrumentation.debugTool.onSetText(this._contentDebugID, '' + contentToUse); + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [this._contentDebugID]); } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { @@ -1020,11 +1021,11 @@ ReactDOMComponent.Mixin = { if (lastContent !== nextContent) { this.updateTextContent('' + nextContent); if (__DEV__) { - var inlinedTextDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); - ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); - ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + nextContent); - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); + this._contentDebugID = this._debugID + '#text'; + ReactInstrumentation.debugTool.onSetIsComposite(this._contentDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(this._contentDebugID, '#text'); + ReactInstrumentation.debugTool.onSetText(this._contentDebugID, '' + nextContent); + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [this._contentDebugID]); } } } else if (nextHtml != null) { @@ -1035,6 +1036,13 @@ ReactDOMComponent.Mixin = { ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); } } else if (nextChildren != null) { + if (__DEV__) { + if (this._contentDebugID) { + ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); + this._contentDebugID = null; + } + } + this.updateChildren(nextChildren, transaction, context); } }, @@ -1096,6 +1104,11 @@ ReactDOMComponent.Mixin = { this._rootNodeID = null; this._domID = null; this._wrapperState = null; + + if (this._contentDebugID) { + ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); + this._contentDebugID = null; + } }, getPublicInstance: function() { diff --git a/src/renderers/shared/reconciler/ReactReconciler.js b/src/renderers/shared/reconciler/ReactReconciler.js index ae7806e4fc0ed..f6741a84edc83 100644 --- a/src/renderers/shared/reconciler/ReactReconciler.js +++ b/src/renderers/shared/reconciler/ReactReconciler.js @@ -53,7 +53,7 @@ var ReactReconciler = { transaction.getReactMountReady().enqueue(attachRefs, internalInstance); } if (__DEV__) { - ReactInstrumentation.debugTool.onMountComponent(internalInstance); + ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); } return markup; }, @@ -76,7 +76,7 @@ var ReactReconciler = { ReactRef.detachRefs(internalInstance, internalInstance._currentElement); internalInstance.unmountComponent(safely); if (__DEV__) { - ReactInstrumentation.debugTool.onUnmountComponent(internalInstance); + ReactInstrumentation.debugTool.onUnmountComponent(internalInstance._debugID); } }, @@ -128,7 +128,7 @@ var ReactReconciler = { } if (__DEV__) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance); + ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); } }, @@ -145,7 +145,7 @@ var ReactReconciler = { ) { internalInstance.performUpdateIfNecessary(transaction); if (__DEV__) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance); + ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); } }, From 835ca9bca3f3657a7fa0d9e7a019f7e14438a6a6 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 20 Apr 2016 20:09:44 +0100 Subject: [PATCH 08/20] Clean up the devtool tree on server rendering --- src/isomorphic/ReactDebugTool.js | 7 +- .../__tests__/ReactDebugTool-test.js | 76 ++++++++++++++----- src/renderers/dom/client/ReactMount.js | 5 ++ .../dom/server/ReactServerRendering.js | 13 +++- src/renderers/dom/shared/ReactDOMComponent.js | 7 ++ .../dom/shared/ReactDOMContainerInfo.js | 2 + .../shared/reconciler/ReactReconciler.js | 5 +- src/test/ReactTestUtils.js | 2 +- 8 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 92fbcc0bcea65..6dfd605b0be47 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -76,8 +76,8 @@ var ReactDebugTool = { onMountRootComponent(debugID) { emitEvent('onMountRootComponent', debugID); }, - onMountComponent(debugID) { - emitEvent('onMountComponent', debugID); + onMountComponent(debugID, nativeContainerDebugID) { + emitEvent('onMountComponent', debugID, nativeContainerDebugID); }, onUpdateComponent(debugID) { emitEvent('onUpdateComponent', debugID); @@ -85,6 +85,9 @@ var ReactDebugTool = { onUnmountComponent(debugID) { emitEvent('onUnmountComponent', debugID); }, + onUnmountNativeContainer(nativeContainerDebugID) { + emitEvent('onUnmountNativeContainer', nativeContainerDebugID); + }, }; ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool); diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index b664ecfd2ab27..93b0d8ab51b5b 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -15,11 +15,12 @@ describe('ReactDebugTool', () => { var React; var ReactDebugTool; var ReactDOM; + var ReactDOMServer; var ReactInstanceMap; - var devtool; - function createDevtool() { + var unmountedContainerIDs = []; + var allChildIDsByContainerID = {}; var tree = {}; function updateTree(id, update) { @@ -55,9 +56,15 @@ describe('ReactDebugTool', () => { return; } - var {childIDs} = item; + var {childIDs, containerID} = item; delete tree[id]; + if (containerID) { + allChildIDsByContainerID[containerID] = allChildIDsByContainerID[containerID].filter( + childID => childID !== id + ); + } + if (childIDs) { childIDs.forEach(purgeTree); } @@ -92,12 +99,31 @@ describe('ReactDebugTool', () => { updateTree(id, item => item.text = text); }, + onMountComponent(id, containerID) { + if (!allChildIDsByContainerID[containerID]) { + allChildIDsByContainerID[containerID] = []; + } + allChildIDsByContainerID[containerID].push(id); + updateTree(id, item => item.containerID = containerID); + }, + onUnmountComponent(id) { purgeTree(id); }, - getRegisteredDebugIDs() { - return Object.keys(tree); + onUnmountNativeContainer(containerID) { + unmountedContainerIDs.push(containerID); + }, + + purgeUnmountedContainers() { + unmountedContainerIDs.forEach(containerID => { + allChildIDsByContainerID[containerID].forEach(purgeTree); + }); + unmountedContainerIDs = []; + }, + + getRegisteredDisplayNames() { + return Object.keys(tree).map(id => tree[id].displayName); }, getTree(rootDebugID, includeOwner) { @@ -112,14 +138,8 @@ describe('ReactDebugTool', () => { React = require('React'); ReactDebugTool = require('ReactDebugTool'); ReactDOM = require('ReactDOM'); + ReactDOMServer = require('ReactDOMServer'); ReactInstanceMap = require('ReactInstanceMap'); - - devtool = createDevtool(); - ReactDebugTool.addDevtool(devtool); - }); - - afterEach(() => { - ReactDebugTool.removeDevtool(devtool); }); function assertTreeMatches(pairs, includeOwner) { @@ -128,28 +148,42 @@ describe('ReactDebugTool', () => { } var currentElement; + var rootInstance; class Wrapper extends React.Component { render() { + rootInstance = ReactInstanceMap.get(this); return currentElement; } } + var clientDevtool = createDevtool(); + ReactDebugTool.addDevtool(clientDevtool); var node = document.createElement('div'); - pairs.forEach(([element, expectedTree]) => { currentElement = element; - - var rootPublicInstance = ReactDOM.render(, node); - var rootInstance = ReactInstanceMap.get(rootPublicInstance); - var actualTree = devtool.getTree( + ReactDOM.render(, node); + expect(clientDevtool.getTree( rootInstance._renderedComponent._debugID, includeOwner - ); - expect(actualTree).toEqual(expectedTree); + )).toEqual(expectedTree); }); - ReactDOM.unmountComponentAtNode(node); - expect(devtool.getRegisteredDebugIDs()).toEqual([]); + ReactDebugTool.removeDevtool(clientDevtool); + expect(clientDevtool.getRegisteredDisplayNames()).toEqual([]); + + var serverDevtool = createDevtool(); + pairs.forEach(([element, expectedTree]) => { + currentElement = element; + ReactDebugTool.addDevtool(serverDevtool); + ReactDOMServer.renderToString(); + ReactDebugTool.removeDevtool(serverDevtool); + expect(serverDevtool.getTree( + rootInstance._renderedComponent._debugID, + includeOwner + )).toEqual(expectedTree); + serverDevtool.purgeUnmountedContainers(); + expect(serverDevtool.getRegisteredDisplayNames()).toEqual([]); + }); } describe('mount', () => { diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 8f62d5b930921..bb6705ba15ffc 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -576,6 +576,11 @@ var ReactMount = { container, false ); + if (__DEV__) { + ReactInstrumentation.debugTool.onUnmountNativeContainer( + prevComponent._nativeContainerInfo._debugID + ); + } return true; }, diff --git a/src/renderers/dom/server/ReactServerRendering.js b/src/renderers/dom/server/ReactServerRendering.js index 5a5d604b7dd8d..8b6e0cb74d1bd 100644 --- a/src/renderers/dom/server/ReactServerRendering.js +++ b/src/renderers/dom/server/ReactServerRendering.js @@ -13,7 +13,9 @@ var ReactDOMContainerInfo = require('ReactDOMContainerInfo'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactElement = require('ReactElement'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); +var ReactReconciler = require('ReactReconciler'); var ReactServerBatchingStrategy = require('ReactServerBatchingStrategy'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); @@ -36,12 +38,19 @@ function renderToStringImpl(element, makeStaticMarkup) { return transaction.perform(function() { var componentInstance = instantiateReactComponent(element); - var markup = componentInstance.mountComponent( + var nativeContainerInfo = ReactDOMContainerInfo(); + var markup = ReactReconciler.mountComponent( + componentInstance, transaction, null, - ReactDOMContainerInfo(), + nativeContainerInfo, emptyObject ); + if (__DEV__) { + ReactInstrumentation.debugTool.onUnmountNativeContainer( + nativeContainerInfo._debugID + ); + } if (!makeStaticMarkup) { markup = ReactMarkupChecksum.addChecksumToMarkup(markup); } diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index f6d4823a1c749..78fc7c9ea6b15 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -719,6 +719,13 @@ ReactDOMComponent.Mixin = { if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node ret = escapeTextContentForBrowser(contentToUse); + if (__DEV__) { + this._contentDebugID = this._debugID + '#text'; + ReactInstrumentation.debugTool.onSetIsComposite(this._contentDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(this._contentDebugID, '#text'); + ReactInstrumentation.debugTool.onSetText(this._contentDebugID, '' + contentToUse); + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [this._contentDebugID]); + } } else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, diff --git a/src/renderers/dom/shared/ReactDOMContainerInfo.js b/src/renderers/dom/shared/ReactDOMContainerInfo.js index 0f74f733a7acb..09a54e5896b60 100644 --- a/src/renderers/dom/shared/ReactDOMContainerInfo.js +++ b/src/renderers/dom/shared/ReactDOMContainerInfo.js @@ -14,6 +14,7 @@ var validateDOMNesting = require('validateDOMNesting'); var DOC_NODE_TYPE = 9; +var nextContainerDebugID = 1; function ReactDOMContainerInfo(topLevelWrapper, node) { var info = { @@ -27,6 +28,7 @@ function ReactDOMContainerInfo(topLevelWrapper, node) { _namespaceURI: node ? node.namespaceURI : null, }; if (__DEV__) { + info._debugID = nextContainerDebugID++; info._ancestorInfo = node ? validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null; } diff --git a/src/renderers/shared/reconciler/ReactReconciler.js b/src/renderers/shared/reconciler/ReactReconciler.js index f6741a84edc83..542bd9524f3f4 100644 --- a/src/renderers/shared/reconciler/ReactReconciler.js +++ b/src/renderers/shared/reconciler/ReactReconciler.js @@ -53,7 +53,10 @@ var ReactReconciler = { transaction.getReactMountReady().enqueue(attachRefs, internalInstance); } if (__DEV__) { - ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); + ReactInstrumentation.debugTool.onMountComponent( + internalInstance._debugID, + nativeContainerInfo._debugID + ); } return markup; }, diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 20f2cb5adb21c..5686744db3096 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -466,7 +466,7 @@ ReactShallowRenderer.prototype._render = function(element, transaction, context) this._instance.receiveComponent(element, transaction, context); } else { var instance = new ShallowComponentWrapper(element); - instance.mountComponent(transaction, null, null, context); + instance.mountComponent(transaction, null, {}, context); this._instance = instance; } }; From b5997c8e7b7ffe1c49c8a287013ce216b7696fce Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 21 Apr 2016 17:10:22 +0100 Subject: [PATCH 09/20] Extract ReactComponentTreeDevtool --- .../__tests__/ReactDebugTool-test.js | 171 +++++------------- .../devtools/ReactComponentTreeDevtool.js | 104 +++++++++++ 2 files changed, 149 insertions(+), 126 deletions(-) create mode 100644 src/isomorphic/devtools/ReactComponentTreeDevtool.js diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index 93b0d8ab51b5b..9051583b2e463 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -17,120 +17,7 @@ describe('ReactDebugTool', () => { var ReactDOM; var ReactDOMServer; var ReactInstanceMap; - - function createDevtool() { - var unmountedContainerIDs = []; - var allChildIDsByContainerID = {}; - var tree = {}; - - function updateTree(id, update) { - if (!tree[id]) { - tree[id] = {}; - } - update(tree[id]); - } - - function getTree(id, includeOwner) { - var item = tree[id]; - var result = { - isComposite: item.isComposite, - displayName: item.displayName, - }; - if (item.childIDs) { - result.children = item.childIDs.map(childID => - getTree(childID, includeOwner) - ); - } - if (item.text != null) { - result.text = item.text; - } - if (includeOwner && item.ownerDebugID) { - result.ownerDisplayName = tree[item.ownerDebugID].displayName; - } - return result; - } - - function purgeTree(id) { - var item = tree[id]; - if (!item) { - return; - } - - var {childIDs, containerID} = item; - delete tree[id]; - - if (containerID) { - allChildIDsByContainerID[containerID] = allChildIDsByContainerID[containerID].filter( - childID => childID !== id - ); - } - - if (childIDs) { - childIDs.forEach(purgeTree); - } - } - - return { - onSetIsComposite(id, isComposite) { - updateTree(id, item => item.isComposite = isComposite); - }, - - onSetDisplayName(id, displayName) { - updateTree(id, item => item.displayName = displayName); - }, - - onSetChildren(id, childIDs) { - childIDs.forEach(childID => { - var childItem = tree[childID]; - expect(childItem).toBeDefined(); - expect(childItem.isComposite).toBeDefined(); - expect(childItem.displayName).toBeDefined(); - expect(childItem.childIDs || childItem.text).toBeDefined(); - }); - - updateTree(id, item => item.childIDs = childIDs); - }, - - onSetOwner(id, ownerDebugID) { - updateTree(id, item => item.ownerDebugID = ownerDebugID); - }, - - onSetText(id, text) { - updateTree(id, item => item.text = text); - }, - - onMountComponent(id, containerID) { - if (!allChildIDsByContainerID[containerID]) { - allChildIDsByContainerID[containerID] = []; - } - allChildIDsByContainerID[containerID].push(id); - updateTree(id, item => item.containerID = containerID); - }, - - onUnmountComponent(id) { - purgeTree(id); - }, - - onUnmountNativeContainer(containerID) { - unmountedContainerIDs.push(containerID); - }, - - purgeUnmountedContainers() { - unmountedContainerIDs.forEach(containerID => { - allChildIDsByContainerID[containerID].forEach(purgeTree); - }); - unmountedContainerIDs = []; - }, - - getRegisteredDisplayNames() { - return Object.keys(tree).map(id => tree[id].displayName); - }, - - getTree(rootDebugID, includeOwner) { - return getTree(rootDebugID, includeOwner); - }, - }; - } + var ReactComponentTreeDevtool; beforeEach(() => { jest.resetModuleRegistry(); @@ -140,15 +27,48 @@ describe('ReactDebugTool', () => { ReactDOM = require('ReactDOM'); ReactDOMServer = require('ReactDOMServer'); ReactInstanceMap = require('ReactInstanceMap'); + ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); + + ReactDebugTool.addDevtool(ReactComponentTreeDevtool); + }); + + afterEach(() => { + ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); }); + function denormalizeTree(tree, rootID, includeOwner) { + var item = tree[rootID]; + var result = { + isComposite: item.isComposite, + displayName: item.displayName, + }; + if (item.childIDs) { + result.children = item.childIDs.map(childID => + denormalizeTree(tree, childID, includeOwner) + ); + } + if (item.text != null) { + result.text = item.text; + } + if (includeOwner && item.ownerDebugID) { + result.ownerDisplayName = tree[item.ownerDebugID].displayName; + } + return result; + } + + function getRegisteredDisplayNames(tree) { + return Object.keys(tree).map(id => tree[id].displayName); + } + function assertTreeMatches(pairs, includeOwner) { if (!Array.isArray(pairs[0])) { pairs = [pairs]; } + var node = document.createElement('div'); var currentElement; var rootInstance; + class Wrapper extends React.Component { render() { rootInstance = ReactInstanceMap.get(this); @@ -156,33 +76,32 @@ describe('ReactDebugTool', () => { } } - var clientDevtool = createDevtool(); - ReactDebugTool.addDevtool(clientDevtool); - var node = document.createElement('div'); pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOM.render(, node); - expect(clientDevtool.getTree( + expect(denormalizeTree( + ReactComponentTreeDevtool.getTree(), rootInstance._renderedComponent._debugID, includeOwner )).toEqual(expectedTree); }); ReactDOM.unmountComponentAtNode(node); - ReactDebugTool.removeDevtool(clientDevtool); - expect(clientDevtool.getRegisteredDisplayNames()).toEqual([]); + expect( + getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) + ).toEqual([]); - var serverDevtool = createDevtool(); pairs.forEach(([element, expectedTree]) => { currentElement = element; - ReactDebugTool.addDevtool(serverDevtool); ReactDOMServer.renderToString(); - ReactDebugTool.removeDevtool(serverDevtool); - expect(serverDevtool.getTree( + expect(denormalizeTree( + ReactComponentTreeDevtool.getTree(), rootInstance._renderedComponent._debugID, includeOwner )).toEqual(expectedTree); - serverDevtool.purgeUnmountedContainers(); - expect(serverDevtool.getRegisteredDisplayNames()).toEqual([]); + ReactComponentTreeDevtool.purgeUnmountedContainers(); + expect( + getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) + ).toEqual([]); }); } diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js new file mode 100644 index 0000000000000..f5d2b784a57d2 --- /dev/null +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -0,0 +1,104 @@ +/** + * Copyright 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactComponentTreeDevtool + */ + +'use strict'; + +var unmountedContainerIDs = []; +var allChildIDsByContainerID = {}; +var tree = {}; + +function updateTree(id, update) { + if (!tree[id]) { + tree[id] = {}; + } + update(tree[id]); +} + +function purgeTree(id) { + var item = tree[id]; + if (!item) { + return; + } + + var {childIDs, containerID} = item; + delete tree[id]; + + if (containerID) { + allChildIDsByContainerID[containerID] = allChildIDsByContainerID[containerID] + .filter(childID => childID !== id); + } + + if (childIDs) { + childIDs.forEach(purgeTree); + } +} + +var ReactComponentTreeDevtool = { + onSetIsComposite(id, isComposite) { + updateTree(id, item => item.isComposite = isComposite); + }, + + onSetDisplayName(id, displayName) { + updateTree(id, item => item.displayName = displayName); + }, + + onSetChildren(id, childIDs) { + childIDs.forEach(childID => { + var childItem = tree[childID]; + expect(childItem).toBeDefined(); + expect(childItem.isComposite).toBeDefined(); + expect(childItem.displayName).toBeDefined(); + expect(childItem.childIDs || childItem.text).toBeDefined(); + }); + + updateTree(id, item => item.childIDs = childIDs); + }, + + onSetOwner(id, ownerDebugID) { + updateTree(id, item => item.ownerDebugID = ownerDebugID); + }, + + onSetText(id, text) { + updateTree(id, item => item.text = text); + }, + + onMountComponent(id, containerID) { + if (!allChildIDsByContainerID[containerID]) { + allChildIDsByContainerID[containerID] = []; + } + allChildIDsByContainerID[containerID].push(id); + updateTree(id, item => item.containerID = containerID); + }, + + onUnmountComponent(id) { + purgeTree(id); + }, + + onUnmountNativeContainer(containerID) { + unmountedContainerIDs.push(containerID); + }, + + purgeUnmountedContainers() { + unmountedContainerIDs.forEach(containerID => { + allChildIDsByContainerID[containerID].forEach(purgeTree); + }); + unmountedContainerIDs = []; + }, + + getTree() { + return Object.keys(tree).reduce((result, key) => { + result[key] = {...tree[key]}; + return result; + }, {}); + }, +}; + +module.exports = ReactComponentTreeDevtool; From f530b977ecf1fd045f686b668435ce2717a729b5 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 21 Apr 2016 17:32:56 +0100 Subject: [PATCH 10/20] Test ReactComponentTreeDevtool specifically --- .../__tests__/ReactComponentTreeDevtool-test.js} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/isomorphic/{__tests__/ReactDebugTool-test.js => devtools/__tests__/ReactComponentTreeDevtool-test.js} (99%) diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js similarity index 99% rename from src/isomorphic/__tests__/ReactDebugTool-test.js rename to src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 9051583b2e463..9c1952668f895 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -11,7 +11,7 @@ 'use strict'; -describe('ReactDebugTool', () => { +describe('ReactComponentTreeDevtool', () => { var React; var ReactDebugTool; var ReactDOM; From ab0ef89ec77962a39e3621ab4ce46cc073d3d7b2 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 21 Apr 2016 17:55:31 +0100 Subject: [PATCH 11/20] Track parentID for ReactPerf --- .../devtools/ReactComponentTreeDevtool.js | 34 +++++++++++++------ .../ReactComponentTreeDevtool-test.js | 18 +++++++--- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index f5d2b784a57d2..af00521163d3b 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -50,20 +50,34 @@ var ReactComponentTreeDevtool = { updateTree(id, item => item.displayName = displayName); }, - onSetChildren(id, childIDs) { - childIDs.forEach(childID => { - var childItem = tree[childID]; - expect(childItem).toBeDefined(); - expect(childItem.isComposite).toBeDefined(); - expect(childItem.displayName).toBeDefined(); - expect(childItem.childIDs || childItem.text).toBeDefined(); + onSetChildren(id, nextChildIDs) { + var prevChildIDs; + updateTree(id, item => { + prevChildIDs = item.childIDs || []; + item.childIDs = nextChildIDs; }); - updateTree(id, item => item.childIDs = childIDs); + prevChildIDs.forEach(prevChildID => { + if (tree[prevChildID] && nextChildIDs.indexOf(prevChildID) === -1) { + tree[prevChildID].parentID = null; + } + }); + + nextChildIDs.forEach(nextChildID => { + var item = tree[nextChildID]; + expect(item).toBeDefined(); + expect(item.isComposite).toBeDefined(); + expect(item.displayName).toBeDefined(); + expect(item.childIDs || item.text).toBeDefined(); + + if (tree[nextChildID] && prevChildIDs.indexOf(nextChildID) === -1) { + tree[nextChildID].parentID = id; + } + }); }, - onSetOwner(id, ownerDebugID) { - updateTree(id, item => item.ownerDebugID = ownerDebugID); + onSetOwner(id, ownerID) { + updateTree(id, item => item.ownerID = ownerID); }, onSetText(id, text) { diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 9c1952668f895..ced1b0f411bac 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -36,22 +36,32 @@ describe('ReactComponentTreeDevtool', () => { ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); }); - function denormalizeTree(tree, rootID, includeOwner) { + function denormalizeTree( + tree, + rootID, + includeOwner = false, + expectedParentID = null + ) { var item = tree[rootID]; var result = { isComposite: item.isComposite, displayName: item.displayName, }; + + if (expectedParentID) { + expect(item.parentID).toBe(expectedParentID); + } + if (item.childIDs) { result.children = item.childIDs.map(childID => - denormalizeTree(tree, childID, includeOwner) + denormalizeTree(tree, childID, includeOwner, rootID) ); } if (item.text != null) { result.text = item.text; } - if (includeOwner && item.ownerDebugID) { - result.ownerDisplayName = tree[item.ownerDebugID].displayName; + if (includeOwner && item.ownerID) { + result.ownerDisplayName = tree[item.ownerID].displayName; } return result; } From 1f2e78359facd976e32d7f1254926b554354efaf Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 21 Apr 2016 18:56:42 +0100 Subject: [PATCH 12/20] No need for a special case here --- src/renderers/shared/reconciler/instantiateReactComponent.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index 07f0766d65191..0e228725e1ce7 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -84,12 +84,10 @@ var nextDebugID = 1; function instantiateReactComponent(node) { var instance; - var isEmpty = false; var isNative = false; var isComposite = false; if (node === null || node === false) { - isEmpty = true; instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; @@ -148,7 +146,7 @@ function instantiateReactComponent(node) { } if (__DEV__) { - instance._debugID = isEmpty ? 0 : nextDebugID++; + instance._debugID = nextDebugID++; var displayName = getDisplayName(instance); ReactInstrumentation.debugTool.onSetIsComposite(instance._debugID, isComposite); ReactInstrumentation.debugTool.onSetDisplayName(instance._debugID, displayName); From e20d366ea85631e6d8326a304c0375c02687eb3c Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 21 Apr 2016 22:23:35 +0100 Subject: [PATCH 13/20] Fix expect() slipping into the non-test code --- .../devtools/ReactComponentTreeDevtool.js | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index af00521163d3b..0f65e1cfc204a 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -11,6 +11,8 @@ 'use strict'; +var invariant = require('invariant'); + var unmountedContainerIDs = []; var allChildIDsByContainerID = {}; var tree = {}; @@ -65,10 +67,27 @@ var ReactComponentTreeDevtool = { nextChildIDs.forEach(nextChildID => { var item = tree[nextChildID]; - expect(item).toBeDefined(); - expect(item.isComposite).toBeDefined(); - expect(item.displayName).toBeDefined(); - expect(item.childIDs || item.text).toBeDefined(); + + invariant( + item, + 'Expected devtool events to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + item.isComposite != null, + 'Expected onSetIsComposite() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + item.displayName != null, + 'Expected onSetDisplayName() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + item.childIDs != null || item.text != null, + 'Expected either onSetChildren() or onSetText() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); if (tree[nextChildID] && prevChildIDs.indexOf(nextChildID) === -1) { tree[nextChildID].parentID = id; From 1ebffa59fe1c465bf10441334c2e60e7a76b32c2 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 22 Apr 2016 02:42:29 +0100 Subject: [PATCH 14/20] ReactComponentTreeDevtool should ignore TopLevelWrapper --- src/isomorphic/ReactDebugTool.js | 3 + .../devtools/ReactComponentTreeDevtool.js | 82 +++++++++++-------- .../ReactComponentTreeDevtool-test.js | 24 +++++- src/renderers/dom/client/ReactMount.js | 6 ++ 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 6dfd605b0be47..8c45afedc2401 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -58,6 +58,9 @@ var ReactDebugTool = { onSetState() { emitEvent('onSetState'); }, + onSetIsTopLevelWrapper(debugID, isTopLevelWrapper) { + emitEvent('onSetIsTopLevelWrapper', debugID, isTopLevelWrapper); + }, onSetIsComposite(debugID, isComposite) { emitEvent('onSetIsComposite', debugID, isComposite); }, diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index 0f65e1cfc204a..fc30ffbbdb76e 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -13,11 +13,15 @@ var invariant = require('invariant'); +var isTopLevelWrapperByID = {}; var unmountedContainerIDs = []; var allChildIDsByContainerID = {}; var tree = {}; function updateTree(id, update) { + if (isTopLevelWrapperByID[id]) { + return; + } if (!tree[id]) { tree[id] = {}; } @@ -44,6 +48,13 @@ function purgeTree(id) { } var ReactComponentTreeDevtool = { + onSetIsTopLevelWrapper(id, isTopLevelWrapper) { + if (isTopLevelWrapper) { + delete tree[id]; + isTopLevelWrapperByID[id] = true; + } + }, + onSetIsComposite(id, isComposite) { updateTree(id, item => item.isComposite = isComposite); }, @@ -53,45 +64,44 @@ var ReactComponentTreeDevtool = { }, onSetChildren(id, nextChildIDs) { - var prevChildIDs; updateTree(id, item => { - prevChildIDs = item.childIDs || []; + var prevChildIDs = item.childIDs || []; item.childIDs = nextChildIDs; - }); - - prevChildIDs.forEach(prevChildID => { - if (tree[prevChildID] && nextChildIDs.indexOf(prevChildID) === -1) { - tree[prevChildID].parentID = null; - } - }); - nextChildIDs.forEach(nextChildID => { - var item = tree[nextChildID]; - - invariant( - item, - 'Expected devtool events to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - item.isComposite != null, - 'Expected onSetIsComposite() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - item.displayName != null, - 'Expected onSetDisplayName() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - invariant( - item.childIDs != null || item.text != null, - 'Expected either onSetChildren() or onSetText() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); - - if (tree[nextChildID] && prevChildIDs.indexOf(nextChildID) === -1) { - tree[nextChildID].parentID = id; - } + prevChildIDs.forEach(prevChildID => { + var prevChild = tree[prevChildID]; + if (prevChild && nextChildIDs.indexOf(prevChildID) === -1) { + prevChild.parentID = null; + } + }); + + nextChildIDs.forEach(nextChildID => { + var nextChild = tree[nextChildID]; + invariant( + nextChild, + 'Expected devtool events to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.isComposite != null, + 'Expected onSetIsComposite() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.displayName != null, + 'Expected onSetDisplayName() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.childIDs != null || nextChild.text != null, + 'Expected either onSetChildren() or onSetText() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + + if (prevChildIDs.indexOf(nextChildID) === -1) { + nextChild.parentID = id; + } + }); }); }, diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index ced1b0f411bac..f8f8a8c4dab1b 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -36,7 +36,7 @@ describe('ReactComponentTreeDevtool', () => { ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); }); - function denormalizeTree( + function explodeTree( tree, rootID, includeOwner = false, @@ -54,7 +54,7 @@ describe('ReactComponentTreeDevtool', () => { if (item.childIDs) { result.children = item.childIDs.map(childID => - denormalizeTree(tree, childID, includeOwner, rootID) + explodeTree(tree, childID, includeOwner, rootID) ); } if (item.text != null) { @@ -89,7 +89,7 @@ describe('ReactComponentTreeDevtool', () => { pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOM.render(, node); - expect(denormalizeTree( + expect(explodeTree( ReactComponentTreeDevtool.getTree(), rootInstance._renderedComponent._debugID, includeOwner @@ -103,7 +103,7 @@ describe('ReactComponentTreeDevtool', () => { pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOMServer.renderToString(); - expect(denormalizeTree( + expect(explodeTree( ReactComponentTreeDevtool.getTree(), rootInstance._renderedComponent._debugID, includeOwner @@ -1831,4 +1831,20 @@ describe('ReactComponentTreeDevtool', () => { }; assertTreeMatches([element, tree], true); }); + + it('ignores top-level wrapper', () => { + var node = document.createElement('div'); + ReactDOM.render(
, node); + expect( + getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) + ).toEqual(['div']); + ReactDOM.render(
, node); + expect( + getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) + ).toEqual(['div']); + ReactDOM.unmountComponentAtNode(node); + expect( + getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) + ).toEqual([]); + }); }); diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index bb6705ba15ffc..87c9e1489a836 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -332,6 +332,12 @@ var ReactMount = { ReactBrowserEventEmitter.ensureScrollValueMonitoring(); var componentInstance = instantiateReactComponent(nextElement); + if (__DEV__) { + ReactInstrumentation.debugTool.onSetIsTopLevelWrapper( + componentInstance._debugID, + true + ); + } // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched From df3c85886e31da8adb5ee670e523edb1230cd299 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 22 Apr 2016 15:45:35 +0100 Subject: [PATCH 15/20] Stop exposing ReactComponentTreeDevtool internal tree directly --- .../devtools/ReactComponentTreeDevtool.js | 43 +++++++--- .../ReactComponentTreeDevtool-test.js | 80 ++++++++----------- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index fc30ffbbdb76e..d057086903969 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -23,7 +23,10 @@ function updateTree(id, update) { return; } if (!tree[id]) { - tree[id] = {}; + tree[id] = { + parentID: null, + childIDs: [], + }; } update(tree[id]); } @@ -41,10 +44,7 @@ function purgeTree(id) { allChildIDsByContainerID[containerID] = allChildIDsByContainerID[containerID] .filter(childID => childID !== id); } - - if (childIDs) { - childIDs.forEach(purgeTree); - } + childIDs.forEach(purgeTree); } var ReactComponentTreeDevtool = { @@ -65,7 +65,7 @@ var ReactComponentTreeDevtool = { onSetChildren(id, nextChildIDs) { updateTree(id, item => { - var prevChildIDs = item.childIDs || []; + var prevChildIDs = item.childIDs; item.childIDs = nextChildIDs; prevChildIDs.forEach(prevChildID => { @@ -136,11 +136,32 @@ var ReactComponentTreeDevtool = { unmountedContainerIDs = []; }, - getTree() { - return Object.keys(tree).reduce((result, key) => { - result[key] = {...tree[key]}; - return result; - }, {}); + isComposite(id) { + return tree[id].isComposite; + }, + + getChildIDs(id) { + return tree[id].childIDs; + }, + + getDisplayName(id) { + return tree[id].displayName; + }, + + getOwnerID(id) { + return tree[id].ownerID; + }, + + getParentID(id) { + return tree[id].parentID; + }, + + getText(id) { + return tree[id].text; + }, + + getRegisteredIDs() { + return Object.keys(tree); }, }; diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index f8f8a8c4dab1b..c9fd70762f9c6 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -36,38 +36,38 @@ describe('ReactComponentTreeDevtool', () => { ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); }); - function explodeTree( - tree, - rootID, - includeOwner = false, - expectedParentID = null - ) { - var item = tree[rootID]; + function getRegisteredDisplayNames() { + return ReactComponentTreeDevtool.getRegisteredIDs() + .map(ReactComponentTreeDevtool.getDisplayName); + } + + function getTree(rootID, includeOwner = false, expectedParentID = null) { var result = { - isComposite: item.isComposite, - displayName: item.displayName, + isComposite: ReactComponentTreeDevtool.isComposite(rootID), + displayName: ReactComponentTreeDevtool.getDisplayName(rootID), }; + var parentID = ReactComponentTreeDevtool.getParentID(rootID); if (expectedParentID) { - expect(item.parentID).toBe(expectedParentID); + expect(parentID).toBe(expectedParentID); } - if (item.childIDs) { - result.children = item.childIDs.map(childID => - explodeTree(tree, childID, includeOwner, rootID) + var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); + var text = ReactComponentTreeDevtool.getText(rootID); + if (text != null) { + result.text = text; + } else { + result.children = childIDs.map(childID => + getTree(childID, includeOwner, rootID) ); } - if (item.text != null) { - result.text = item.text; - } - if (includeOwner && item.ownerID) { - result.ownerDisplayName = tree[item.ownerID].displayName; + + var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); + if (includeOwner && ownerID) { + result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID); } - return result; - } - function getRegisteredDisplayNames(tree) { - return Object.keys(tree).map(id => tree[id].displayName); + return result; } function assertTreeMatches(pairs, includeOwner) { @@ -89,29 +89,21 @@ describe('ReactComponentTreeDevtool', () => { pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOM.render(, node); - expect(explodeTree( - ReactComponentTreeDevtool.getTree(), - rootInstance._renderedComponent._debugID, - includeOwner - )).toEqual(expectedTree); + expect( + getTree(rootInstance._renderedComponent._debugID, includeOwner) + ).toEqual(expectedTree); }); ReactDOM.unmountComponentAtNode(node); - expect( - getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) - ).toEqual([]); + expect(getRegisteredDisplayNames()).toEqual([]); pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOMServer.renderToString(); - expect(explodeTree( - ReactComponentTreeDevtool.getTree(), - rootInstance._renderedComponent._debugID, - includeOwner - )).toEqual(expectedTree); - ReactComponentTreeDevtool.purgeUnmountedContainers(); expect( - getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) - ).toEqual([]); + getTree(rootInstance._renderedComponent._debugID, includeOwner) + ).toEqual(expectedTree); + ReactComponentTreeDevtool.purgeUnmountedContainers(); + expect(getRegisteredDisplayNames()).toEqual([]); }); } @@ -1835,16 +1827,10 @@ describe('ReactComponentTreeDevtool', () => { it('ignores top-level wrapper', () => { var node = document.createElement('div'); ReactDOM.render(
, node); - expect( - getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) - ).toEqual(['div']); + expect(getRegisteredDisplayNames()).toEqual(['div']); ReactDOM.render(
, node); - expect( - getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) - ).toEqual(['div']); + expect(getRegisteredDisplayNames()).toEqual(['div']); ReactDOM.unmountComponentAtNode(node); - expect( - getRegisteredDisplayNames(ReactComponentTreeDevtool.getTree()) - ).toEqual([]); + expect(getRegisteredDisplayNames()).toEqual([]); }); }); From 900a588f6309ce1c8ab1670df95f7981ccf04277 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 23 Apr 2016 02:23:16 +0100 Subject: [PATCH 16/20] Add safeguards to ReactComponentTreeDevtool --- .../devtools/ReactComponentTreeDevtool.js | 40 ++++++++++++------- .../ReactComponentTreeDevtool-test.js | 9 ++--- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index d057086903969..bbea5ea53f97e 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -13,19 +13,19 @@ var invariant = require('invariant'); -var isTopLevelWrapperByID = {}; var unmountedContainerIDs = []; var allChildIDsByContainerID = {}; var tree = {}; function updateTree(id, update) { - if (isTopLevelWrapperByID[id]) { - return; - } if (!tree[id]) { tree[id] = { parentID: null, + ownerID: null, + text: null, childIDs: [], + displayName: 'Unknown', + isTopLevelWrapper: false, }; } update(tree[id]); @@ -49,10 +49,7 @@ function purgeTree(id) { var ReactComponentTreeDevtool = { onSetIsTopLevelWrapper(id, isTopLevelWrapper) { - if (isTopLevelWrapper) { - delete tree[id]; - isTopLevelWrapperByID[id] = true; - } + updateTree(id, item => item.isTopLevelWrapper = isTopLevelWrapper); }, onSetIsComposite(id, isComposite) { @@ -64,6 +61,10 @@ var ReactComponentTreeDevtool = { }, onSetChildren(id, nextChildIDs) { + if (ReactComponentTreeDevtool.isTopLevelWrapper(id)) { + return; + } + updateTree(id, item => { var prevChildIDs = item.childIDs; item.childIDs = nextChildIDs; @@ -137,27 +138,38 @@ var ReactComponentTreeDevtool = { }, isComposite(id) { - return tree[id].isComposite; + var item = tree[id]; + return item ? item.isComposite : false; + }, + + isTopLevelWrapper(id) { + var item = tree[id]; + return item ? item.isTopLevelWrapper : false; }, getChildIDs(id) { - return tree[id].childIDs; + var item = tree[id]; + return item ? item.childIDs : []; }, getDisplayName(id) { - return tree[id].displayName; + var item = tree[id]; + return item ? item.displayName : 'Unknown'; }, getOwnerID(id) { - return tree[id].ownerID; + var item = tree[id]; + return item ? item.ownerID : null; }, getParentID(id) { - return tree[id].parentID; + var item = tree[id]; + return item ? item.parentID : null; }, getText(id) { - return tree[id].text; + var item = tree[id]; + return item ? item.text : null; }, getRegisteredIDs() { diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index c9fd70762f9c6..07596b3a82c41 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -38,6 +38,7 @@ describe('ReactComponentTreeDevtool', () => { function getRegisteredDisplayNames() { return ReactComponentTreeDevtool.getRegisteredIDs() + .filter(id => !ReactComponentTreeDevtool.isTopLevelWrapper(id)) .map(ReactComponentTreeDevtool.getDisplayName); } @@ -48,9 +49,7 @@ describe('ReactComponentTreeDevtool', () => { }; var parentID = ReactComponentTreeDevtool.getParentID(rootID); - if (expectedParentID) { - expect(parentID).toBe(expectedParentID); - } + expect(parentID).toBe(expectedParentID); var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); var text = ReactComponentTreeDevtool.getText(rootID); @@ -90,7 +89,7 @@ describe('ReactComponentTreeDevtool', () => { currentElement = element; ReactDOM.render(, node); expect( - getTree(rootInstance._renderedComponent._debugID, includeOwner) + getTree(rootInstance._debugID, includeOwner).children[0] ).toEqual(expectedTree); }); ReactDOM.unmountComponentAtNode(node); @@ -100,7 +99,7 @@ describe('ReactComponentTreeDevtool', () => { currentElement = element; ReactDOMServer.renderToString(); expect( - getTree(rootInstance._renderedComponent._debugID, includeOwner) + getTree(rootInstance._debugID, includeOwner).children[0] ).toEqual(expectedTree); ReactComponentTreeDevtool.purgeUnmountedContainers(); expect(getRegisteredDisplayNames()).toEqual([]); From 829558b8b031f7906866949a1f9428154b49ed74 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sat, 23 Apr 2016 22:27:40 +0100 Subject: [PATCH 17/20] Refactor how native container ID is stored --- .../devtools/ReactComponentTreeDevtool.js | 38 +++++++------------ .../ReactComponentTreeDevtool-test.js | 2 +- 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index bbea5ea53f97e..45154d3f97863 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -14,12 +14,12 @@ var invariant = require('invariant'); var unmountedContainerIDs = []; -var allChildIDsByContainerID = {}; var tree = {}; function updateTree(id, update) { if (!tree[id]) { tree[id] = { + nativeContainerID: null, parentID: null, ownerID: null, text: null, @@ -33,18 +33,11 @@ function updateTree(id, update) { function purgeTree(id) { var item = tree[id]; - if (!item) { - return; + if (item) { + var {childIDs} = item; + delete tree[id]; + childIDs.forEach(purgeTree); } - - var {childIDs, containerID} = item; - delete tree[id]; - - if (containerID) { - allChildIDsByContainerID[containerID] = allChildIDsByContainerID[containerID] - .filter(childID => childID !== id); - } - childIDs.forEach(purgeTree); } var ReactComponentTreeDevtool = { @@ -114,27 +107,24 @@ var ReactComponentTreeDevtool = { updateTree(id, item => item.text = text); }, - onMountComponent(id, containerID) { - if (!allChildIDsByContainerID[containerID]) { - allChildIDsByContainerID[containerID] = []; - } - allChildIDsByContainerID[containerID].push(id); - updateTree(id, item => item.containerID = containerID); + onMountComponent(id, nativeContainerID) { + updateTree(id, item => item.nativeContainerID = nativeContainerID); }, onUnmountComponent(id) { purgeTree(id); }, - onUnmountNativeContainer(containerID) { - unmountedContainerIDs.push(containerID); + onUnmountNativeContainer(nativeContainerID) { + unmountedContainerIDs.push(nativeContainerID); }, - purgeUnmountedContainers() { - unmountedContainerIDs.forEach(containerID => { - allChildIDsByContainerID[containerID].forEach(purgeTree); - }); + purgeComponentsFromUnmountedContainers() { + var unmountedIDs = Object.keys(tree).filter(id => + unmountedContainerIDs.indexOf(tree[id].nativeContainerID) !== -1 + ); unmountedContainerIDs = []; + unmountedIDs.forEach(purgeTree); }, isComposite(id) { diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 07596b3a82c41..445c20146a91b 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -101,7 +101,7 @@ describe('ReactComponentTreeDevtool', () => { expect( getTree(rootInstance._debugID, includeOwner).children[0] ).toEqual(expectedTree); - ReactComponentTreeDevtool.purgeUnmountedContainers(); + ReactComponentTreeDevtool.purgeComponentsFromUnmountedContainers(); expect(getRegisteredDisplayNames()).toEqual([]); }); } From 9f16003e69fd1e24a8bf98a8905c4e4c0d595154 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 24 Apr 2016 01:53:53 +0100 Subject: [PATCH 18/20] Assert that unmounted instances are in the tree until purged --- .../devtools/ReactComponentTreeDevtool.js | 2 +- .../ReactComponentTreeDevtool-test.js | 121 +++++++++++++++--- 2 files changed, 106 insertions(+), 17 deletions(-) diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index 45154d3f97863..a9fa5033a649a 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -119,7 +119,7 @@ var ReactComponentTreeDevtool = { unmountedContainerIDs.push(nativeContainerID); }, - purgeComponentsFromUnmountedContainers() { + purgeUnmountedComponents() { var unmountedIDs = Object.keys(tree).filter(id => unmountedContainerIDs.indexOf(tree[id].nativeContainerID) !== -1 ); diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 445c20146a91b..9f4b37a5f19fa 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -42,34 +42,43 @@ describe('ReactComponentTreeDevtool', () => { .map(ReactComponentTreeDevtool.getDisplayName); } - function getTree(rootID, includeOwner = false, expectedParentID = null) { + function getTree(rootID, options = {}) { + var { + includeOwnerDisplayName = false, + includeParentDisplayName = false, + expectedParentID = null, + } = options; + var result = { isComposite: ReactComponentTreeDevtool.isComposite(rootID), displayName: ReactComponentTreeDevtool.getDisplayName(rootID), }; + var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); var parentID = ReactComponentTreeDevtool.getParentID(rootID); expect(parentID).toBe(expectedParentID); + if (includeParentDisplayName && parentID) { + result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID); + } + if (includeOwnerDisplayName && ownerID) { + result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID); + } + var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); var text = ReactComponentTreeDevtool.getText(rootID); if (text != null) { result.text = text; } else { result.children = childIDs.map(childID => - getTree(childID, includeOwner, rootID) + getTree(childID, {...options, expectedParentID: rootID }) ); } - var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); - if (includeOwner && ownerID) { - result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID); - } - return result; } - function assertTreeMatches(pairs, includeOwner) { + function assertTreeMatches(pairs, options) { if (!Array.isArray(pairs[0])) { pairs = [pairs]; } @@ -85,23 +94,31 @@ describe('ReactComponentTreeDevtool', () => { } } + function getActualTree() { + return getTree(rootInstance._debugID, options).children[0]; + } + pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOM.render(, node); - expect( - getTree(rootInstance._debugID, includeOwner).children[0] - ).toEqual(expectedTree); + expect(getActualTree()).toEqual(expectedTree); }); ReactDOM.unmountComponentAtNode(node); + + var lastExpectedTree = pairs[pairs.length - 1][1]; + expect(getActualTree()).toEqual(lastExpectedTree); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getActualTree()).toBe(undefined); expect(getRegisteredDisplayNames()).toEqual([]); pairs.forEach(([element, expectedTree]) => { currentElement = element; ReactDOMServer.renderToString(); - expect( - getTree(rootInstance._debugID, includeOwner).children[0] - ).toEqual(expectedTree); - ReactComponentTreeDevtool.purgeComponentsFromUnmountedContainers(); + expect(getActualTree()).toEqual(expectedTree); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getActualTree()).toBe(undefined); expect(getRegisteredDisplayNames()).toEqual([]); }); } @@ -1820,7 +1837,77 @@ describe('ReactComponentTreeDevtool', () => { }], }], }; - assertTreeMatches([element, tree], true); + assertTreeMatches([element, tree], {includeOwnerDisplayName: true}); + }); + + it.only('preserves unmounted components until purge', () => { + var node = document.createElement('div'); + var renderBar = true; + var fooInstance; + var barInstance; + + class Foo extends React.Component { + render() { + fooInstance = ReactInstanceMap.get(this); + return renderBar ? : null; + } + } + + class Bar extends React.Component { + render() { + barInstance = ReactInstanceMap.get(this); + return null; + } + } + + ReactDOM.render(, node); + expect( + getTree(barInstance._debugID, { + includeParentDisplayName: true, + expectedParentID: fooInstance._debugID + }) + ).toEqual({ + isComposite: true, + displayName: 'Bar', + parentDisplayName: 'Foo', + children: [], + }); + + renderBar = false; + ReactDOM.render(, node); + expect( + getTree(barInstance._debugID, { + includeParentDisplayName: true, + expectedParentID: fooInstance._debugID + }) + ).toEqual({ + isComposite: true, + displayName: 'Bar', + parentDisplayName: 'Foo', + children: [], + }); + + ReactDOM.unmountComponentAtNode(node); + expect( + getTree(barInstance._debugID, { + includeParentDisplayName: true, + expectedParentID: fooInstance._debugID, + }) + ).toEqual({ + isComposite: true, + displayName: 'Bar', + parentDisplayName: 'Foo', + children: [], + }); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect( + getTree(barInstance._debugID, {includeParentDisplayName: true}) + ).toEqual({ + isComposite: false, + displayName: 'Unknown', + children: [], + }); }); it('ignores top-level wrapper', () => { @@ -1830,6 +1917,8 @@ describe('ReactComponentTreeDevtool', () => { ReactDOM.render(
, node); expect(getRegisteredDisplayNames()).toEqual(['div']); ReactDOM.unmountComponentAtNode(node); + expect(getRegisteredDisplayNames()).toEqual(['div']); + ReactComponentTreeDevtool.purgeUnmountedComponents(); expect(getRegisteredDisplayNames()).toEqual([]); }); }); From acd67c368d1ff66d80657094f136e87b36413210 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 24 Apr 2016 02:55:59 +0100 Subject: [PATCH 19/20] Remove isComposite and hide TopLevelWrapper --- src/isomorphic/ReactDebugTool.js | 35 ++- .../devtools/ReactComponentTreeDevtool.js | 77 +++--- .../ReactComponentTreeDevtool-test.js | 247 ++---------------- src/renderers/dom/client/ReactMount.js | 18 +- .../dom/server/ReactServerRendering.js | 7 +- src/renderers/dom/shared/ReactDOMComponent.js | 32 ++- .../dom/shared/ReactDOMContainerInfo.js | 2 - .../reconciler/ReactCompositeComponent.js | 31 ++- .../shared/reconciler/ReactMultiChild.js | 25 +- .../shared/reconciler/ReactReconciler.js | 5 +- .../reconciler/instantiateReactComponent.js | 14 +- src/test/ReactTestUtils.js | 2 +- 12 files changed, 143 insertions(+), 352 deletions(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 8c45afedc2401..37dd2d55f5f55 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -37,6 +37,11 @@ function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) { } } +// This can be removed once TopLevelWrapper is gone. +function isTopLevelWrapper(debugID) { + return debugID === 0; +} + var ReactDebugTool = { addDevtool(devtool) { eventHandlers.push(devtool); @@ -58,17 +63,16 @@ var ReactDebugTool = { onSetState() { emitEvent('onSetState'); }, - onSetIsTopLevelWrapper(debugID, isTopLevelWrapper) { - emitEvent('onSetIsTopLevelWrapper', debugID, isTopLevelWrapper); - }, - onSetIsComposite(debugID, isComposite) { - emitEvent('onSetIsComposite', debugID, isComposite); - }, onSetDisplayName(debugID, displayName) { emitEvent('onSetDisplayName', debugID, displayName); }, + onSetIsEmpty(debugID, isEmpty) { + emitEvent('onSetIsEmpty', debugID, isEmpty); + }, onSetChildren(debugID, childDebugIDs) { - emitEvent('onSetChildren', debugID, childDebugIDs); + if (!isTopLevelWrapper(debugID)) { + emitEvent('onSetChildren', debugID, childDebugIDs); + } }, onSetOwner(debugID, ownerDebugID) { emitEvent('onSetOwner', debugID, ownerDebugID); @@ -79,17 +83,20 @@ var ReactDebugTool = { onMountRootComponent(debugID) { emitEvent('onMountRootComponent', debugID); }, - onMountComponent(debugID, nativeContainerDebugID) { - emitEvent('onMountComponent', debugID, nativeContainerDebugID); + onMountComponent(debugID) { + if (!isTopLevelWrapper(debugID)) { + emitEvent('onMountComponent', debugID); + } }, onUpdateComponent(debugID) { - emitEvent('onUpdateComponent', debugID); + if (!isTopLevelWrapper(debugID)) { + emitEvent('onUpdateComponent', debugID); + } }, onUnmountComponent(debugID) { - emitEvent('onUnmountComponent', debugID); - }, - onUnmountNativeContainer(nativeContainerDebugID) { - emitEvent('onUnmountNativeContainer', nativeContainerDebugID); + if (!isTopLevelWrapper(debugID)) { + emitEvent('onUnmountComponent', debugID); + } }, }; diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index a9fa5033a649a..0f578fbc35e7d 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -13,40 +13,36 @@ var invariant = require('invariant'); -var unmountedContainerIDs = []; var tree = {}; +var rootIDs = []; function updateTree(id, update) { if (!tree[id]) { tree[id] = { - nativeContainerID: null, parentID: null, ownerID: null, text: null, childIDs: [], displayName: 'Unknown', - isTopLevelWrapper: false, + isMounted: false, + isEmpty: false, }; } update(tree[id]); } -function purgeTree(id) { +function purgeDeep(id) { var item = tree[id]; if (item) { var {childIDs} = item; delete tree[id]; - childIDs.forEach(purgeTree); + childIDs.forEach(purgeDeep); } } var ReactComponentTreeDevtool = { - onSetIsTopLevelWrapper(id, isTopLevelWrapper) { - updateTree(id, item => item.isTopLevelWrapper = isTopLevelWrapper); - }, - - onSetIsComposite(id, isComposite) { - updateTree(id, item => item.isComposite = isComposite); + onSetIsEmpty(id, isEmpty) { + updateTree(id, item => item.isEmpty = isEmpty); }, onSetDisplayName(id, displayName) { @@ -54,21 +50,10 @@ var ReactComponentTreeDevtool = { }, onSetChildren(id, nextChildIDs) { - if (ReactComponentTreeDevtool.isTopLevelWrapper(id)) { - return; - } - updateTree(id, item => { var prevChildIDs = item.childIDs; item.childIDs = nextChildIDs; - prevChildIDs.forEach(prevChildID => { - var prevChild = tree[prevChildID]; - if (prevChild && nextChildIDs.indexOf(prevChildID) === -1) { - prevChild.parentID = null; - } - }); - nextChildIDs.forEach(nextChildID => { var nextChild = tree[nextChildID]; invariant( @@ -76,11 +61,6 @@ var ReactComponentTreeDevtool = { 'Expected devtool events to fire for the child ' + 'before its parent includes it in onSetChildren().' ); - invariant( - nextChild.isComposite != null, - 'Expected onSetIsComposite() to fire for the child ' + - 'before its parent includes it in onSetChildren().' - ); invariant( nextChild.displayName != null, 'Expected onSetDisplayName() to fire for the child ' + @@ -88,7 +68,12 @@ var ReactComponentTreeDevtool = { ); invariant( nextChild.childIDs != null || nextChild.text != null, - 'Expected either onSetChildren() or onSetText() to fire for the child ' + + 'Expected onSetChildren() or onSetText() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.isMounted, + 'Expected onMountComponent() to fire for the child ' + 'before its parent includes it in onSetChildren().' ); @@ -107,39 +92,33 @@ var ReactComponentTreeDevtool = { updateTree(id, item => item.text = text); }, - onMountComponent(id, nativeContainerID) { - updateTree(id, item => item.nativeContainerID = nativeContainerID); + onMountComponent(id) { + updateTree(id, item => item.isMounted = true); }, - onUnmountComponent(id) { - purgeTree(id); + onMountRootComponent(id) { + rootIDs.push(id); }, - onUnmountNativeContainer(nativeContainerID) { - unmountedContainerIDs.push(nativeContainerID); + onUnmountComponent(id) { + updateTree(id, item => item.isMounted = false); + rootIDs = rootIDs.filter(rootID => rootID !== id); }, purgeUnmountedComponents() { - var unmountedIDs = Object.keys(tree).filter(id => - unmountedContainerIDs.indexOf(tree[id].nativeContainerID) !== -1 - ); - unmountedContainerIDs = []; - unmountedIDs.forEach(purgeTree); + Object.keys(tree) + .filter(id => !tree[id].isMounted) + .forEach(purgeDeep); }, - isComposite(id) { + isMounted(id) { var item = tree[id]; - return item ? item.isComposite : false; - }, - - isTopLevelWrapper(id) { - var item = tree[id]; - return item ? item.isTopLevelWrapper : false; + return item ? item.isMounted : false; }, getChildIDs(id) { var item = tree[id]; - return item ? item.childIDs : []; + return item ? item.childIDs.filter(childID => !tree[childID].isEmpty) : []; }, getDisplayName(id) { @@ -162,6 +141,10 @@ var ReactComponentTreeDevtool = { return item ? item.text : null; }, + getRootIDs() { + return rootIDs; + }, + getRegisteredIDs() { return Object.keys(tree); }, diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 9f4b37a5f19fa..48f3b9d3d9f3e 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -36,9 +36,13 @@ describe('ReactComponentTreeDevtool', () => { ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); }); + function getRootDisplayNames() { + return ReactComponentTreeDevtool.getRootIDs() + .map(ReactComponentTreeDevtool.getDisplayName); + } + function getRegisteredDisplayNames() { return ReactComponentTreeDevtool.getRegisteredIDs() - .filter(id => !ReactComponentTreeDevtool.isTopLevelWrapper(id)) .map(ReactComponentTreeDevtool.getDisplayName); } @@ -50,7 +54,6 @@ describe('ReactComponentTreeDevtool', () => { } = options; var result = { - isComposite: ReactComponentTreeDevtool.isComposite(rootID), displayName: ReactComponentTreeDevtool.getDisplayName(rootID), }; @@ -102,14 +105,14 @@ describe('ReactComponentTreeDevtool', () => { currentElement = element; ReactDOM.render(, node); expect(getActualTree()).toEqual(expectedTree); + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getActualTree()).toEqual(expectedTree); }); ReactDOM.unmountComponentAtNode(node); - var lastExpectedTree = pairs[pairs.length - 1][1]; - expect(getActualTree()).toEqual(lastExpectedTree); - ReactComponentTreeDevtool.purgeUnmountedComponents(); expect(getActualTree()).toBe(undefined); + expect(getRootDisplayNames()).toEqual([]); expect(getRegisteredDisplayNames()).toEqual([]); pairs.forEach(([element, expectedTree]) => { @@ -119,6 +122,7 @@ describe('ReactComponentTreeDevtool', () => { ReactComponentTreeDevtool.purgeUnmountedComponents(); expect(getActualTree()).toBe(undefined); + expect(getRootDisplayNames()).toEqual([]); expect(getRegisteredDisplayNames()).toEqual([]); }); } @@ -145,18 +149,14 @@ describe('ReactComponentTreeDevtool', () => { var element =
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Bar', children: [], }, { - isComposite: true, displayName: 'Baz', children: [], }, { - isComposite: true, displayName: 'Unknown', children: [], }], @@ -185,18 +185,14 @@ describe('ReactComponentTreeDevtool', () => { var element =
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Bar', children: [], }, { - isComposite: true, displayName: 'Baz', children: [], }, { - isComposite: true, // Note: Ideally fallback name should be consistent (e.g. "Unknown") displayName: 'ReactComponent', children: [], @@ -232,18 +228,14 @@ describe('ReactComponentTreeDevtool', () => { var element =
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Bar', children: [], }, { - isComposite: true, displayName: 'Baz', children: [], }, { - isComposite: true, displayName: 'Unknown', children: [], }], @@ -266,18 +258,14 @@ describe('ReactComponentTreeDevtool', () => { var element =
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Bar', children: [], }, { - isComposite: true, displayName: 'Baz', children: [], }, { - isComposite: true, displayName: 'Unknown', children: [], }], @@ -298,26 +286,20 @@ describe('ReactComponentTreeDevtool', () => {
); var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: 'p', children: [{ - isComposite: false, displayName: 'span', children: [{ - isComposite: false, displayName: '#text', text: 'Hi!', }], }, { - isComposite: false, displayName: '#text', text: 'Wow.', }], }, { - isComposite: false, displayName: 'hr', children: [], }], @@ -334,10 +316,8 @@ describe('ReactComponentTreeDevtool', () => { var element = ; var tree = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -378,44 +358,33 @@ describe('ReactComponentTreeDevtool', () => { var element = ; var tree = { - isComposite: true, displayName: 'Baz', children: [{ - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Qux', children: [], }], }, { - isComposite: true, displayName: 'Bar', children: [{ - isComposite: false, displayName: 'h1', children: [{ - isComposite: false, displayName: 'span', children: [{ - isComposite: false, displayName: '#text', text: 'Hi,', }], }, { - isComposite: false, displayName: '#text', text: 'Mom', }], }], }, { - isComposite: false, displayName: 'a', children: [{ - isComposite: false, displayName: '#text', text: 'Click me.', }], @@ -433,7 +402,6 @@ describe('ReactComponentTreeDevtool', () => { } var element = ; var tree = { - isComposite: true, displayName: 'Foo', children: [], }; @@ -448,7 +416,6 @@ describe('ReactComponentTreeDevtool', () => { } var element = ; var tree = { - isComposite: true, displayName: 'Foo', children: [], }; @@ -458,14 +425,11 @@ describe('ReactComponentTreeDevtool', () => { it('reports text nodes as children', () => { var element =
{'1'}{2}
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: '1', }, { - isComposite: false, displayName: '#text', text: '2', }], @@ -476,10 +440,8 @@ describe('ReactComponentTreeDevtool', () => { it('reports a single text node as a child', () => { var element =
{'1'}
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: '1', }], @@ -490,10 +452,8 @@ describe('ReactComponentTreeDevtool', () => { it('reports a single number node as a child', () => { var element =
{42}
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: '42', }], @@ -504,10 +464,8 @@ describe('ReactComponentTreeDevtool', () => { it('reports a zero as a child', () => { var element =
{0}
; var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: '0', }], @@ -529,21 +487,16 @@ describe('ReactComponentTreeDevtool', () => {
); var tree = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'hi', }, { - isComposite: false, displayName: '#text', text: '42', }, { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -555,7 +508,6 @@ describe('ReactComponentTreeDevtool', () => { it('reports html content as no children', () => { var element =
; var tree = { - isComposite: false, displayName: 'div', children: [], }; @@ -568,10 +520,8 @@ describe('ReactComponentTreeDevtool', () => { it('updates text of a single text child', () => { var elementBefore =
Hi.
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -579,10 +529,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
Bye.
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -597,17 +545,14 @@ describe('ReactComponentTreeDevtool', () => { it('updates from no children to a single text child', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
Hi.
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -622,10 +567,8 @@ describe('ReactComponentTreeDevtool', () => { it('updates from a single text child to no children', () => { var elementBefore =
Hi.
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -633,7 +576,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -647,17 +589,14 @@ describe('ReactComponentTreeDevtool', () => { it('updates from html content to a single text child', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
Hi.
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -672,10 +611,8 @@ describe('ReactComponentTreeDevtool', () => { it('updates from a single text child to html content', () => { var elementBefore =
Hi.
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -683,7 +620,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -697,21 +633,17 @@ describe('ReactComponentTreeDevtool', () => { it('updates from no children to multiple text children', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
{'Hi.'}{'Bye.'}
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -726,14 +658,11 @@ describe('ReactComponentTreeDevtool', () => { it('updates from multiple text children to no children', () => { var elementBefore =
{'Hi.'}{'Bye.'}
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -741,7 +670,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -755,21 +683,17 @@ describe('ReactComponentTreeDevtool', () => { it('updates from html content to multiple text children', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
{'Hi.'}{'Bye.'}
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -784,14 +708,11 @@ describe('ReactComponentTreeDevtool', () => { it('updates from multiple text children to html content', () => { var elementBefore =
{'Hi.'}{'Bye.'}
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -799,7 +720,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -813,14 +733,12 @@ describe('ReactComponentTreeDevtool', () => { it('updates from html content to no children', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -834,14 +752,12 @@ describe('ReactComponentTreeDevtool', () => { it('updates from no children to html content', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -855,10 +771,8 @@ describe('ReactComponentTreeDevtool', () => { it('updates from one text child to multiple text children', () => { var elementBefore =
Hi.
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -866,14 +780,11 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
{'Hi.'}{'Bye.'}
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -888,14 +799,11 @@ describe('ReactComponentTreeDevtool', () => { it('updates from multiple text children to one text child', () => { var elementBefore =
{'Hi.'}{'Bye.'}
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -903,10 +811,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
Hi.
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -920,14 +826,11 @@ describe('ReactComponentTreeDevtool', () => { it('updates text nodes when reordering', () => { var elementBefore =
{'Hi.'}{'Bye.'}
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }, { - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -935,14 +838,11 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
{'Bye.'}{'Hi.'}
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Bye.', }, { - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -961,21 +861,16 @@ describe('ReactComponentTreeDevtool', () => {
); var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], }, { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -989,21 +884,16 @@ describe('ReactComponentTreeDevtool', () => {
); var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Bye.', }], }, { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -1024,21 +914,16 @@ describe('ReactComponentTreeDevtool', () => {
); var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], }, { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Bye.', }], @@ -1052,21 +937,16 @@ describe('ReactComponentTreeDevtool', () => {
); var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Bye.', }], }, { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], @@ -1090,10 +970,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [], }], @@ -1101,10 +979,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1123,13 +999,10 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1138,13 +1011,10 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'span', children: [], }], @@ -1164,17 +1034,14 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [], }; var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [], }], @@ -1193,10 +1060,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [], }], @@ -1204,7 +1069,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: false, displayName: 'div', children: [], }; @@ -1229,21 +1093,16 @@ describe('ReactComponentTreeDevtool', () => {
); var tree1 = { - isComposite: false, displayName: 'div', children: [{ - isComposite: false, displayName: '#text', text: 'hi', }, { - isComposite: false, displayName: '#text', text: '42', }, { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1259,18 +1118,14 @@ describe('ReactComponentTreeDevtool', () => {
); var tree2 = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], }, { - isComposite: false, displayName: '#text', text: 'hi', }], @@ -1282,13 +1137,10 @@ describe('ReactComponentTreeDevtool', () => {
); var tree3 = { - isComposite: false, displayName: 'div', children: [{ - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1311,10 +1163,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1322,10 +1172,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = ; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'span', children: [], }], @@ -1344,17 +1192,14 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = {null}; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [], }; var elementAfter =
; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1373,10 +1218,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1384,7 +1227,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = {null}; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [], }; @@ -1406,10 +1248,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1417,10 +1257,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = ; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1443,10 +1281,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = ; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1454,10 +1290,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1480,17 +1314,14 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = {null}; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [], }; var elementAfter = ; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1513,10 +1344,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = ; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1524,7 +1353,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = {null}; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [], }; @@ -1546,10 +1374,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1557,10 +1383,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = ; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'span', children: [], }], @@ -1581,17 +1405,14 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = {null}; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [], }; var elementAfter =
; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1612,10 +1433,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1623,7 +1442,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = {null}; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [], }; @@ -1649,10 +1467,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore =
; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1660,10 +1476,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = ; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1690,10 +1504,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = ; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1701,10 +1513,8 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter =
; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', children: [], }], @@ -1731,17 +1541,14 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = {null}; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [], }; var elementAfter = ; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1768,10 +1575,8 @@ describe('ReactComponentTreeDevtool', () => { var elementBefore = ; var treeBefore = { - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', children: [], }], @@ -1779,7 +1584,6 @@ describe('ReactComponentTreeDevtool', () => { var elementAfter = {null}; var treeAfter = { - isComposite: true, displayName: 'Foo', children: [], }; @@ -1806,30 +1610,23 @@ describe('ReactComponentTreeDevtool', () => { // because they are not created from real elements. var element =
; var tree = { - isComposite: false, displayName: 'article', children: [{ - isComposite: true, displayName: 'Foo', children: [{ - isComposite: true, displayName: 'Bar', ownerDisplayName: 'Foo', children: [{ - isComposite: false, displayName: 'div', ownerDisplayName: 'Bar', children: [{ - isComposite: false, displayName: 'h1', ownerDisplayName: 'Foo', children: [{ - isComposite: false, displayName: '#text', text: 'Hi.', }], }, { - isComposite: false, displayName: '#text', text: ' Mom', }], @@ -1840,7 +1637,7 @@ describe('ReactComponentTreeDevtool', () => { assertTreeMatches([element, tree], {includeOwnerDisplayName: true}); }); - it.only('preserves unmounted components until purge', () => { + it('preserves unmounted components until purge', () => { var node = document.createElement('div'); var renderBar = true; var fooInstance; @@ -1864,10 +1661,9 @@ describe('ReactComponentTreeDevtool', () => { expect( getTree(barInstance._debugID, { includeParentDisplayName: true, - expectedParentID: fooInstance._debugID + expectedParentID: fooInstance._debugID, }) ).toEqual({ - isComposite: true, displayName: 'Bar', parentDisplayName: 'Foo', children: [], @@ -1878,10 +1674,9 @@ describe('ReactComponentTreeDevtool', () => { expect( getTree(barInstance._debugID, { includeParentDisplayName: true, - expectedParentID: fooInstance._debugID + expectedParentID: fooInstance._debugID, }) ).toEqual({ - isComposite: true, displayName: 'Bar', parentDisplayName: 'Foo', children: [], @@ -1894,7 +1689,6 @@ describe('ReactComponentTreeDevtool', () => { expectedParentID: fooInstance._debugID, }) ).toEqual({ - isComposite: true, displayName: 'Bar', parentDisplayName: 'Foo', children: [], @@ -1904,21 +1698,28 @@ describe('ReactComponentTreeDevtool', () => { expect( getTree(barInstance._debugID, {includeParentDisplayName: true}) ).toEqual({ - isComposite: false, displayName: 'Unknown', children: [], }); }); - it('ignores top-level wrapper', () => { + it('does not report top-level wrapper as a root', () => { var node = document.createElement('div'); + ReactDOM.render(
, node); - expect(getRegisteredDisplayNames()).toEqual(['div']); + expect(getRootDisplayNames()).toEqual(['div']); + ReactDOM.render(
, node); - expect(getRegisteredDisplayNames()).toEqual(['div']); + expect(getRootDisplayNames()).toEqual(['div']); + ReactDOM.unmountComponentAtNode(node); - expect(getRegisteredDisplayNames()).toEqual(['div']); + expect(getRootDisplayNames()).toEqual([]); + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getRootDisplayNames()).toEqual([]); + + // This currently contains TopLevelWrapper until purge + // so we only check it at the very end. expect(getRegisteredDisplayNames()).toEqual([]); }); }); diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 87c9e1489a836..1984e146f3f27 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -332,11 +332,11 @@ var ReactMount = { ReactBrowserEventEmitter.ensureScrollValueMonitoring(); var componentInstance = instantiateReactComponent(nextElement); + if (__DEV__) { - ReactInstrumentation.debugTool.onSetIsTopLevelWrapper( - componentInstance._debugID, - true - ); + // Mute future events from the top level wrapper. + // It is an implementation detail that devtools should not know about. + componentInstance._debugID = 0; } // The initial render is synchronous but any updates that happen during @@ -355,7 +355,10 @@ var ReactMount = { instancesByReactRootID[wrapperID] = componentInstance; if (__DEV__) { - ReactInstrumentation.debugTool.onMountRootComponent(componentInstance._debugID); + // The instance here is TopLevelWrapper so we report mount for its child. + ReactInstrumentation.debugTool.onMountRootComponent( + componentInstance._renderedComponent._debugID + ); } return componentInstance; @@ -582,11 +585,6 @@ var ReactMount = { container, false ); - if (__DEV__) { - ReactInstrumentation.debugTool.onUnmountNativeContainer( - prevComponent._nativeContainerInfo._debugID - ); - } return true; }, diff --git a/src/renderers/dom/server/ReactServerRendering.js b/src/renderers/dom/server/ReactServerRendering.js index 8b6e0cb74d1bd..2d30f1a18d4ae 100644 --- a/src/renderers/dom/server/ReactServerRendering.js +++ b/src/renderers/dom/server/ReactServerRendering.js @@ -38,17 +38,16 @@ function renderToStringImpl(element, makeStaticMarkup) { return transaction.perform(function() { var componentInstance = instantiateReactComponent(element); - var nativeContainerInfo = ReactDOMContainerInfo(); var markup = ReactReconciler.mountComponent( componentInstance, transaction, null, - nativeContainerInfo, + ReactDOMContainerInfo(), emptyObject ); if (__DEV__) { - ReactInstrumentation.debugTool.onUnmountNativeContainer( - nativeContainerInfo._debugID + ReactInstrumentation.debugTool.onUnmountComponent( + componentInstance._debugID ); } if (!makeStaticMarkup) { diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 78fc7c9ea6b15..6545f12d83390 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -36,6 +36,7 @@ var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChild = require('ReactMultiChild'); var ReactPerf = require('ReactPerf'); +var emptyFunction = require('emptyFunction'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -246,6 +247,20 @@ function optionPostMount() { ReactDOMOption.postMountWrapper(inst); } +var setContentChildForInstrumentation = emptyFunction; +if (__DEV__) { + setContentChildForInstrumentation = function(contentToUse) { + var debugID = this._debugID; + var contentDebugID = debugID + '#text'; + this._contentDebugID = contentDebugID; + ReactInstrumentation.debugTool.onSetIsEmpty(contentDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(contentDebugID, '#text'); + ReactInstrumentation.debugTool.onSetText(contentDebugID, '' + contentToUse); + ReactInstrumentation.debugTool.onMountComponent(contentDebugID); + ReactInstrumentation.debugTool.onSetChildren(debugID, [contentDebugID]); + }; +} + // There are so many media events, it makes sense to just // maintain a list rather than create a `trapBubbledEvent` for each var mediaEvents = { @@ -720,11 +735,7 @@ ReactDOMComponent.Mixin = { // TODO: Validate that text is allowed as a child of this node ret = escapeTextContentForBrowser(contentToUse); if (__DEV__) { - this._contentDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetIsComposite(this._contentDebugID, false); - ReactInstrumentation.debugTool.onSetDisplayName(this._contentDebugID, '#text'); - ReactInstrumentation.debugTool.onSetText(this._contentDebugID, '' + contentToUse); - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [this._contentDebugID]); + setContentChildForInstrumentation.call(this, contentToUse); } } else if (childrenToUse != null) { var mountImages = this.mountChildren( @@ -766,11 +777,7 @@ ReactDOMComponent.Mixin = { if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node if (__DEV__) { - this._contentDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetIsComposite(this._contentDebugID, false); - ReactInstrumentation.debugTool.onSetDisplayName(this._contentDebugID, '#text'); - ReactInstrumentation.debugTool.onSetText(this._contentDebugID, '' + contentToUse); - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [this._contentDebugID]); + setContentChildForInstrumentation.call(this, contentToUse); } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { @@ -1029,10 +1036,7 @@ ReactDOMComponent.Mixin = { this.updateTextContent('' + nextContent); if (__DEV__) { this._contentDebugID = this._debugID + '#text'; - ReactInstrumentation.debugTool.onSetIsComposite(this._contentDebugID, false); - ReactInstrumentation.debugTool.onSetDisplayName(this._contentDebugID, '#text'); - ReactInstrumentation.debugTool.onSetText(this._contentDebugID, '' + nextContent); - ReactInstrumentation.debugTool.onSetChildren(this._debugID, [this._contentDebugID]); + setContentChildForInstrumentation.call(this, nextContent); } } } else if (nextHtml != null) { diff --git a/src/renderers/dom/shared/ReactDOMContainerInfo.js b/src/renderers/dom/shared/ReactDOMContainerInfo.js index 09a54e5896b60..0f74f733a7acb 100644 --- a/src/renderers/dom/shared/ReactDOMContainerInfo.js +++ b/src/renderers/dom/shared/ReactDOMContainerInfo.js @@ -14,7 +14,6 @@ var validateDOMNesting = require('validateDOMNesting'); var DOC_NODE_TYPE = 9; -var nextContainerDebugID = 1; function ReactDOMContainerInfo(topLevelWrapper, node) { var info = { @@ -28,7 +27,6 @@ function ReactDOMContainerInfo(topLevelWrapper, node) { _namespaceURI: node ? node.namespaceURI : null, }; if (__DEV__) { - info._debugID = nextContainerDebugID++; info._ancestorInfo = node ? validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null; } diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index 103e21d89e4b0..411eca482753f 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -377,14 +377,6 @@ var ReactCompositeComponentMixin = { this._renderedComponent = this._instantiateReactComponent( renderedElement ); - if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - this._renderedNodeType === ReactNodeTypes.EMPTY ? - [] : - [this._renderedComponent._debugID] - ); - } var markup = ReactReconciler.mountComponent( this._renderedComponent, @@ -394,6 +386,13 @@ var ReactCompositeComponentMixin = { this._processChildContext(context) ); + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + [this._renderedComponent._debugID] + ); + } + return markup; }, @@ -861,14 +860,6 @@ var ReactCompositeComponentMixin = { this._renderedComponent = this._instantiateReactComponent( nextRenderedElement ); - if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - this._renderedNodeType === ReactNodeTypes.EMPTY ? - [] : - [this._renderedComponent._debugID] - ); - } var nextMarkup = ReactReconciler.mountComponent( this._renderedComponent, @@ -877,6 +868,14 @@ var ReactCompositeComponentMixin = { this._nativeContainerInfo, this._processChildContext(context) ); + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + [this._renderedComponent._debugID] + ); + } + this._replaceNodeWithMarkup(oldNativeNode, nextMarkup); } }, diff --git a/src/renderers/shared/reconciler/ReactMultiChild.js b/src/renderers/shared/reconciler/ReactMultiChild.js index 2a7ccb70e36e8..474ddc63dc3ef 100644 --- a/src/renderers/shared/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/reconciler/ReactMultiChild.js @@ -19,6 +19,7 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactReconciler = require('ReactReconciler'); var ReactChildReconciler = require('ReactChildReconciler'); +var emptyFunction = require('emptyFunction'); var flattenChildren = require('flattenChildren'); var invariant = require('invariant'); @@ -138,6 +139,16 @@ function processQueue(inst, updateQueue) { ); } +var setChildrenForInstrumentation = emptyFunction; +if (__DEV__) { + setChildrenForInstrumentation = function(children) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + children ? Object.keys(children).map(key => children[key]._debugID) : [] + ); + }; +} + /** * ReactMultiChild are capable of reconciling multiple children. * @@ -234,12 +245,7 @@ var ReactMultiChild = { } if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - children ? - Object.keys(children).map(key => children[key]._debugID) : - [] - ); + setChildrenForInstrumentation.call(this, children); } return mountImages; @@ -371,12 +377,7 @@ var ReactMultiChild = { this._renderedChildren = nextChildren; if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - nextChildren ? - Object.keys(nextChildren).map(key => nextChildren[key]._debugID) : - [] - ); + setChildrenForInstrumentation.call(this, nextChildren); } }, diff --git a/src/renderers/shared/reconciler/ReactReconciler.js b/src/renderers/shared/reconciler/ReactReconciler.js index 542bd9524f3f4..f6741a84edc83 100644 --- a/src/renderers/shared/reconciler/ReactReconciler.js +++ b/src/renderers/shared/reconciler/ReactReconciler.js @@ -53,10 +53,7 @@ var ReactReconciler = { transaction.getReactMountReady().enqueue(attachRefs, internalInstance); } if (__DEV__) { - ReactInstrumentation.debugTool.onMountComponent( - internalInstance._debugID, - nativeContainerInfo._debugID - ); + ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); } return markup; }, diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index 0e228725e1ce7..0943964ed3935 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -86,8 +86,10 @@ function instantiateReactComponent(node) { var isNative = false; var isComposite = false; + var isEmpty = false; if (node === null || node === false) { + isEmpty = true; instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; @@ -146,16 +148,18 @@ function instantiateReactComponent(node) { } if (__DEV__) { - instance._debugID = nextDebugID++; + var debugID = nextDebugID++; + instance._debugID = debugID; + var displayName = getDisplayName(instance); - ReactInstrumentation.debugTool.onSetIsComposite(instance._debugID, isComposite); - ReactInstrumentation.debugTool.onSetDisplayName(instance._debugID, displayName); + ReactInstrumentation.debugTool.onSetDisplayName(debugID, displayName); + ReactInstrumentation.debugTool.onSetIsEmpty(debugID, isEmpty); if (isNative || isComposite) { - ReactInstrumentation.debugTool.onSetChildren(instance._debugID, []); + ReactInstrumentation.debugTool.onSetChildren(debugID, []); } var owner = node && node._owner; if (owner) { - ReactInstrumentation.debugTool.onSetOwner(instance._debugID, owner._debugID); + ReactInstrumentation.debugTool.onSetOwner(debugID, owner._debugID); } } diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 5686744db3096..20f2cb5adb21c 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -466,7 +466,7 @@ ReactShallowRenderer.prototype._render = function(element, transaction, context) this._instance.receiveComponent(element, transaction, context); } else { var instance = new ShallowComponentWrapper(element); - instance.mountComponent(transaction, null, {}, context); + instance.mountComponent(transaction, null, null, context); this._instance = instance; } }; From a9e0896af88bd42401ea76eb1c97376f492682a0 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 26 Apr 2016 01:01:03 +0100 Subject: [PATCH 20/20] Mute devtool events for TopLevelWrapper and empty components --- src/isomorphic/ReactDebugTool.js | 24 ++++--------------- .../devtools/ReactComponentTreeDevtool.js | 7 +----- src/renderers/dom/shared/ReactDOMComponent.js | 1 - .../reconciler/ReactCompositeComponent.js | 24 ++++++++++++------- .../shared/reconciler/ReactReconciler.js | 16 +++++++++---- .../reconciler/instantiateReactComponent.js | 17 +++---------- 6 files changed, 36 insertions(+), 53 deletions(-) diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 37dd2d55f5f55..49c6cb550078f 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -37,11 +37,6 @@ function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) { } } -// This can be removed once TopLevelWrapper is gone. -function isTopLevelWrapper(debugID) { - return debugID === 0; -} - var ReactDebugTool = { addDevtool(devtool) { eventHandlers.push(devtool); @@ -66,13 +61,8 @@ var ReactDebugTool = { onSetDisplayName(debugID, displayName) { emitEvent('onSetDisplayName', debugID, displayName); }, - onSetIsEmpty(debugID, isEmpty) { - emitEvent('onSetIsEmpty', debugID, isEmpty); - }, onSetChildren(debugID, childDebugIDs) { - if (!isTopLevelWrapper(debugID)) { - emitEvent('onSetChildren', debugID, childDebugIDs); - } + emitEvent('onSetChildren', debugID, childDebugIDs); }, onSetOwner(debugID, ownerDebugID) { emitEvent('onSetOwner', debugID, ownerDebugID); @@ -84,19 +74,13 @@ var ReactDebugTool = { emitEvent('onMountRootComponent', debugID); }, onMountComponent(debugID) { - if (!isTopLevelWrapper(debugID)) { - emitEvent('onMountComponent', debugID); - } + emitEvent('onMountComponent', debugID); }, onUpdateComponent(debugID) { - if (!isTopLevelWrapper(debugID)) { - emitEvent('onUpdateComponent', debugID); - } + emitEvent('onUpdateComponent', debugID); }, onUnmountComponent(debugID) { - if (!isTopLevelWrapper(debugID)) { - emitEvent('onUnmountComponent', debugID); - } + emitEvent('onUnmountComponent', debugID); }, }; diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js index 0f578fbc35e7d..bdba5b378f31e 100644 --- a/src/isomorphic/devtools/ReactComponentTreeDevtool.js +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -25,7 +25,6 @@ function updateTree(id, update) { childIDs: [], displayName: 'Unknown', isMounted: false, - isEmpty: false, }; } update(tree[id]); @@ -41,10 +40,6 @@ function purgeDeep(id) { } var ReactComponentTreeDevtool = { - onSetIsEmpty(id, isEmpty) { - updateTree(id, item => item.isEmpty = isEmpty); - }, - onSetDisplayName(id, displayName) { updateTree(id, item => item.displayName = displayName); }, @@ -118,7 +113,7 @@ var ReactComponentTreeDevtool = { getChildIDs(id) { var item = tree[id]; - return item ? item.childIDs.filter(childID => !tree[childID].isEmpty) : []; + return item ? item.childIDs : []; }, getDisplayName(id) { diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 6545f12d83390..d2c77fe1cd7b7 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -253,7 +253,6 @@ if (__DEV__) { var debugID = this._debugID; var contentDebugID = debugID + '#text'; this._contentDebugID = contentDebugID; - ReactInstrumentation.debugTool.onSetIsEmpty(contentDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(contentDebugID, '#text'); ReactInstrumentation.debugTool.onSetText(contentDebugID, '' + contentToUse); ReactInstrumentation.debugTool.onMountComponent(contentDebugID); diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index 411eca482753f..26d1c8fb6e709 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -387,10 +387,14 @@ var ReactCompositeComponentMixin = { ); if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - [this._renderedComponent._debugID] - ); + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedComponent._debugID !== 0 ? + [this._renderedComponent._debugID] : + [] + ); + } } return markup; @@ -870,10 +874,14 @@ var ReactCompositeComponentMixin = { ); if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - [this._renderedComponent._debugID] - ); + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedComponent._debugID !== 0 ? + [this._renderedComponent._debugID] : + [] + ); + } } this._replaceNodeWithMarkup(oldNativeNode, nextMarkup); diff --git a/src/renderers/shared/reconciler/ReactReconciler.js b/src/renderers/shared/reconciler/ReactReconciler.js index f6741a84edc83..d2a2dbcac851e 100644 --- a/src/renderers/shared/reconciler/ReactReconciler.js +++ b/src/renderers/shared/reconciler/ReactReconciler.js @@ -53,7 +53,9 @@ var ReactReconciler = { transaction.getReactMountReady().enqueue(attachRefs, internalInstance); } if (__DEV__) { - ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); + } } return markup; }, @@ -76,7 +78,9 @@ var ReactReconciler = { ReactRef.detachRefs(internalInstance, internalInstance._currentElement); internalInstance.unmountComponent(safely); if (__DEV__) { - ReactInstrumentation.debugTool.onUnmountComponent(internalInstance._debugID); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onUnmountComponent(internalInstance._debugID); + } } }, @@ -128,7 +132,9 @@ var ReactReconciler = { } if (__DEV__) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + } } }, @@ -145,7 +151,9 @@ var ReactReconciler = { ) { internalInstance.performUpdateIfNecessary(transaction); if (__DEV__) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + } } }, diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index 0943964ed3935..db22ea3b91df7 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -84,12 +84,8 @@ var nextDebugID = 1; function instantiateReactComponent(node) { var instance; - var isNative = false; - var isComposite = false; - var isEmpty = false; - - if (node === null || node === false) { - isEmpty = true; + var isEmpty = node === null || node === false; + if (isEmpty) { instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; @@ -104,16 +100,13 @@ function instantiateReactComponent(node) { // Special case string values if (typeof element.type === 'string') { - isNative = true; instance = ReactNativeComponent.createInternalComponent(element); } else if (isInternalComponentType(element.type)) { - isComposite = true; // This is temporarily available for custom components that are not string // representations. I.e. ART. Once those are updated to use the string // representation, we can drop this code path. instance = new element.type(element); } else { - isComposite = true; instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { @@ -148,15 +141,11 @@ function instantiateReactComponent(node) { } if (__DEV__) { - var debugID = nextDebugID++; + var debugID = isEmpty ? 0 : nextDebugID++; instance._debugID = debugID; var displayName = getDisplayName(instance); ReactInstrumentation.debugTool.onSetDisplayName(debugID, displayName); - ReactInstrumentation.debugTool.onSetIsEmpty(debugID, isEmpty); - if (isNative || isComposite) { - ReactInstrumentation.debugTool.onSetChildren(debugID, []); - } var owner = node && node._owner; if (owner) { ReactInstrumentation.debugTool.onSetOwner(debugID, owner._debugID);