@samitouri / QOS-React-2 / commits / ce528457a4

ReactDOMComponentTree

New module! With this, we have a new module that uses the component tree to store DOM nodes. Most of the logic in this file relates to markup adoption -- if we were to drop that (along with server rendering) this file could just be a `return inst._nativeNode;`. This works with useCreateElement only because we need to reference each node as it's created. Events is now the only thing using ReactMount.getNode -- I'll introduce pointers back from the DOM nodes (and a `ReactDOMComponentTree.getInstanceFromNode`) and make that work.

Ben Alpert committed Oct 15, 2015 at 16:09 UTC ce528457a49306cf7b0fb4008e9c15aa8d68b93e
15 files changed +289 -137
src/renderers/dom/client/ReactDOMComponentTree.js new
+122
@@ -0,0 +1,122 @@
1 +/**
2 + * Copyright 2013-2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactDOMComponentTree
10 + */
11 +
12 +'use strict';
13 +
14 +var DOMProperty = require('DOMProperty');
15 +var ReactDOMComponentFlags = require('ReactDOMComponentFlags');
16 +
17 +var invariant = require('invariant');
18 +
19 +var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
20 +var Flags = ReactDOMComponentFlags;
21 +
22 +/**
23 + * Drill down (through composites and empty components) until we get a native or
24 + * native text component.
25 + *
26 + * This is pretty polymorphic but unavoidable with the current structure we have
27 + * for `_renderedChildren`.
28 + */
29 +function getRenderedNativeOrTextFromComponent(component) {
30 + var rendered;
31 + while ((rendered = component._renderedComponent)) {
32 + component = rendered;
33 + }
34 + return component;
35 +}
36 +
37 +/**
38 + * Populate `_nativeNode` on the rendered native/text component with the given
39 + * DOM node. The passed `inst` can be a composite.
40 + */
41 +function precacheNode(inst, node) {
42 + var nativeInst = getRenderedNativeOrTextFromComponent(inst);
43 + nativeInst._nativeNode = node;
44 +}
45 +
46 +/**
47 + * Populate `_nativeNode` on each child of `inst`, assuming that the children
48 + * match up with the DOM (element) children of `node`.
49 + *
50 + * We cache entire levels at once to avoid an n^2 problem where we access the
51 + * children of a node sequentially and have to walk from the start to our target
52 + * node every time.
53 + *
54 + * Since we update `_renderedChildren` and the actual DOM at (slightly)
55 + * different times, we could race here and not get the
56 + */
57 +function precacheChildNodes(inst, node) {
58 + if (inst._flags & Flags.hasCachedChildNodes) {
59 + return;
60 + }
61 + var children = inst._renderedChildren;
62 + var childNode = node.firstChild;
63 + outer: for (var name in children) {
64 + if (!children.hasOwnProperty(name)) {
65 + continue;
66 + }
67 + var childInst = children[name];
68 + var childID = childInst._rootNodeID;
69 + if (childID == null) {
70 + // We're currently unmounting this child in ReactMultiChild; skip it.
71 + continue;
72 + }
73 + // We assume the child nodes are in the same order as the child instances.
74 + for (; childNode !== null; childNode = childNode.nextSibling) {
75 + if (childNode.nodeType === 1 &&
76 + childNode.getAttribute(ATTR_NAME) === childID) {
77 + precacheNode(childInst, childNode);
78 + continue outer;
79 + }
80 + }
81 + // We reached the end of the DOM children without finding an ID match.
82 + invariant(false, 'Unable to find element with ID %s.', childID);
83 + }
84 + inst._flags |= Flags.hasCachedChildNodes;
85 +}
86 +
87 +/**
88 + * Given a ReactDOMComponent or ReactDOMTextComponent, return the corresponding
89 + * DOM node.
90 + */
91 +function getNodeFromInstance(inst) {
92 + if (inst._nativeNode) {
93 + return inst._nativeNode;
94 + }
95 +
96 + // Walk up the tree until we find an ancestor whose DOM node we have cached.
97 + var parents = [];
98 + while (!inst._nativeNode) {
99 + parents.push(inst);
100 + invariant(
101 + inst._nativeParent,
102 + 'React DOM tree root should always have a node reference.'
103 + );
104 + inst = inst._nativeParent;
105 + }
106 +
107 + // Now parents contains each ancestor that does *not* have a cached native
108 + // node, and `inst` is the deepest ancestor that does.
109 + for (; parents.length; inst = parents.pop()) {
110 + precacheChildNodes(inst, inst._nativeNode);
111 + }
112 +
113 + return inst._nativeNode;
114 +}
115 +
116 +var ReactDOMComponentTree = {
117 + getNodeFromInstance: getNodeFromInstance,
118 + precacheChildNodes: precacheChildNodes,
119 + precacheNode: precacheNode,
120 +};
121 +
122 +module.exports = ReactDOMComponentTree;
src/renderers/dom/client/ReactDOMIDOperations.js
+4 -44
@@ -13,56 +13,14 @@
13 'use strict';
14
15 var DOMChildrenOperations = require('DOMChildrenOperations');
16 -var DOMPropertyOperations = require('DOMPropertyOperations');
17 -var ReactMount = require('ReactMount');
16 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
17 var ReactPerf = require('ReactPerf');
18
20 -var invariant = require('invariant');
21 -
22 -/**
23 - * Errors for properties that should not be updated with `updatePropertyByID()`.
24 - *
25 - * @type {object}
26 - * @private
27 - */
28 -var INVALID_PROPERTY_ERRORS = {
29 - dangerouslySetInnerHTML:
30 - '`dangerouslySetInnerHTML` must be set using `updateInnerHTMLByID()`.',
31 - style: '`style` must be set using `updateStylesByID()`.',
32 -};
33 -
19 /**
20 * Operations used to process updates to DOM nodes.
21 */
22 var ReactDOMIDOperations = {
23
39 - /**
40 - * Updates a DOM node with new property values. This should only be used to
41 - * update DOM properties in `DOMProperty`.
42 - *
43 - * @param {string} id ID of the node to update.
44 - * @param {string} name A valid property name, see `DOMProperty`.
45 - * @param {*} value New value of the property.
46 - * @internal
47 - */
48 - updatePropertyByID: function(id, name, value) {
49 - var node = ReactMount.getNode(id);
50 - invariant(
51 - !INVALID_PROPERTY_ERRORS.hasOwnProperty(name),
52 - 'updatePropertyByID(...): %s',
53 - INVALID_PROPERTY_ERRORS[name]
54 - );
55 -
56 - // If we're updating to null or undefined, we should remove the property
57 - // from the DOM node instead of inadvertantly setting to a string. This
58 - // brings us in line with the same behavior we have on initial render.
59 - if (value != null) {
60 - DOMPropertyOperations.setValueForProperty(node, name, value);
61 - } else {
62 - DOMPropertyOperations.deleteValueForProperty(node, name);
63 - }
64 - },
65 -
24 /**
25 * Updates a component's children by processing a series of updates.
26 *
@@ -72,7 +30,9 @@ var ReactDOMIDOperations = {
30 */
31 dangerouslyProcessChildrenUpdates: function(updates, markup) {
32 for (var i = 0; i < updates.length; i++) {
75 - updates[i].parentNode = ReactMount.getNode(updates[i].parentID);
33 + var update = updates[i];
34 + var node = ReactDOMComponentTree.getNodeFromInstance(update.parentInst);
35 + update.parentNode = node;
36 }
37 DOMChildrenOperations.processUpdates(updates, markup);
38 },
src/renderers/dom/client/ReactMount.js
+5
@@ -16,6 +16,7 @@ var DOMLazyTree = require('DOMLazyTree');
16 var DOMProperty = require('DOMProperty');
17 var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
18 var ReactCurrentOwner = require('ReactCurrentOwner');
19 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
20 var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
21 var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
22 var ReactElement = require('ReactElement');
@@ -267,6 +268,7 @@ function mountComponentIntoNode(
268 ReactMount._mountImageIntoNode(
269 markup,
270 container,
271 + componentInstance,
272 shouldReuseMarkup,
273 transaction
274 );
@@ -951,6 +953,7 @@ var ReactMount = {
953 _mountImageIntoNode: function(
954 markup,
955 container,
956 + instance,
957 shouldReuseMarkup,
958 transaction
959 ) {
@@ -966,6 +969,7 @@ var ReactMount = {
969 if (shouldReuseMarkup) {
970 var rootElement = getReactRootElementInContainer(container);
971 if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) {
972 + ReactDOMComponentTree.precacheNode(instance, rootElement);
973 return;
974 } else {
975 var checksum = rootElement.getAttribute(
@@ -1049,6 +1053,7 @@ var ReactMount = {
1053 DOMLazyTree.insertTreeBefore(container, markup, null);
1054 } else {
1055 setInnerHTML(container, markup);
1056 + ReactDOMComponentTree.precacheNode(instance, container.firstChild);
1057 }
1058 },
1059
src/renderers/dom/client/__tests__/ReactDOMComponentTree-test.js new
+62
@@ -0,0 +1,62 @@
1 +/**
2 + * Copyright 2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @emails react-core
10 + */
11 +
12 +'use strict';
13 +
14 +describe('ReactDOMComponentTree', function() {
15 + var React;
16 + var ReactDOM;
17 + var ReactDOMServer;
18 +
19 + function renderMarkupIntoDocument(elt) {
20 + var container = document.createElement('div');
21 + // Force server-rendering path:
22 + container.innerHTML = ReactDOMServer.renderToString(elt);
23 + return ReactDOM.render(elt, container);
24 + }
25 +
26 + beforeEach(function() {
27 + React = require('React');
28 + ReactDOM = require('ReactDOM');
29 + ReactDOMServer = require('ReactDOMServer');
30 + });
31 +
32 + it('finds nodes for instances', function() {
33 + // This is a little hard to test directly. But refs rely on it -- so we
34 + // check that we can find a ref at arbitrary points in the tree, even if
35 + // other nodes don't have a ref.
36 + var Component = React.createClass({
37 + render: function() {
38 + var toRef = this.props.toRef;
39 + return (
40 + <div ref={toRef === 'div' ? 'target' : null}>
41 + <h1 ref={toRef === 'h1' ? 'target' : null}>hello</h1>
42 + <p ref={toRef === 'p' ? 'target' : null}>
43 + <input ref={toRef === 'input' ? 'target' : null} />
44 + </p>
45 + goodbye.
46 + </div>
47 + );
48 + },
49 + });
50 +
51 + function renderAndGetRef(toRef) {
52 + var inst = renderMarkupIntoDocument(<Component toRef={toRef} />);
53 + return inst.refs.target.nodeName;
54 + }
55 +
56 + expect(renderAndGetRef('div')).toBe('DIV');
57 + expect(renderAndGetRef('h1')).toBe('H1');
58 + expect(renderAndGetRef('p')).toBe('P');
59 + expect(renderAndGetRef('input')).toBe('INPUT');
60 + });
61 +
62 +});
src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js
+1 -31
@@ -12,42 +12,16 @@
12 'use strict';
13
14 describe('ReactDOMIDOperations', function() {
15 - var DOMPropertyOperations = require('DOMPropertyOperations');
15 var ReactDOMIDOperations = require('ReactDOMIDOperations');
17 - var ReactMount = require('ReactMount');
16 var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
19 - var keyOf = require('keyOf');
20 -
21 - it('should disallow updating special properties', function() {
22 - spyOn(ReactMount, 'getNode');
23 - spyOn(DOMPropertyOperations, 'setValueForProperty');
24 -
25 - expect(function() {
26 - ReactDOMIDOperations.updatePropertyByID(
27 - 'testID',
28 - keyOf({dangerouslySetInnerHTML: null}),
29 - {__html: 'testContent'}
30 - );
31 - }).toThrow();
32 -
33 - expect(
34 - ReactMount.getNode.argsForCall[0][0]
35 - ).toBe('testID');
36 -
37 - expect(
38 - DOMPropertyOperations.setValueForProperty.calls.length
39 - ).toBe(0);
40 - });
17
18 it('should update innerHTML and preserve whitespace', function() {
19 var stubNode = document.createElement('div');
44 - spyOn(ReactMount, 'getNode').andReturn(stubNode);
45 -
20 var html = '\n \t <span> \n testContent \t </span> \n \t';
21
22 ReactDOMIDOperations.dangerouslyProcessChildrenUpdates(
23 [{
50 - parentID: 'testID',
24 + parentInst: {_nativeNode: stubNode},
25 parentNode: null,
26 type: ReactMultiChildUpdateTypes.SET_MARKUP,
27 markupIndex: null,
@@ -58,10 +32,6 @@ describe('ReactDOMIDOperations', function() {
32 []
33 );
34
61 - expect(
62 - ReactMount.getNode.argsForCall[0][0]
63 - ).toBe('testID');
64 -
35 expect(stubNode.innerHTML).toBe(html);
36 });
37 });
src/renderers/dom/client/findDOMNode.js
+2 -2
@@ -13,7 +13,7 @@
13 'use strict';
14
15 var ReactCurrentOwner = require('ReactCurrentOwner');
16 -var ReactDOMComponent = require('ReactDOMComponent');
16 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
17 var ReactInstanceMap = require('ReactInstanceMap');
18 var ReactNodeTypes = require('ReactNodeTypes');
19
@@ -66,7 +66,7 @@ function findDOMNode(componentOrElement) {
66 var inst = ReactInstanceMap.get(componentOrElement);
67 if (inst) {
68 inst = getNativeComponentFromComposite(inst);
69 - return inst ? ReactDOMComponent.getNodeFromInstance(inst) : null;
69 + return inst ? ReactDOMComponentTree.getNodeFromInstance(inst) : null;
70 }
71
72 if (typeof componentOrElement.render === 'function') {
src/renderers/dom/client/wrappers/AutoFocusUtils.js
+2 -2
@@ -12,13 +12,13 @@
12
13 'use strict';
14
15 -var ReactMount = require('ReactMount');
15 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
16
17 var focusNode = require('focusNode');
18
19 var AutoFocusUtils = {
20 focusDOMComponent: function() {
21 - focusNode(ReactMount.getNode(this._rootNodeID));
21 + focusNode(ReactDOMComponentTree.getNodeFromInstance(this));
22 },
23 };
24
src/renderers/dom/client/wrappers/ReactDOMInput.js
+7 -6
@@ -11,8 +11,9 @@
11
12 'use strict';
13
14 -var ReactDOMIDOperations = require('ReactDOMIDOperations');
14 +var DOMPropertyOperations = require('DOMPropertyOperations');
15 var LinkedValueUtils = require('LinkedValueUtils');
16 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
17 var ReactMount = require('ReactMount');
18 var ReactUpdates = require('ReactUpdates');
19
@@ -131,8 +132,8 @@ var ReactDOMInput = {
132 // TODO: Shouldn't this be getChecked(props)?
133 var checked = props.checked;
134 if (checked != null) {
134 - ReactDOMIDOperations.updatePropertyByID(
135 - inst._rootNodeID,
135 + DOMPropertyOperations.setValueForProperty(
136 + ReactDOMComponentTree.getNodeFromInstance(inst),
137 'checked',
138 checked || false
139 );
@@ -142,8 +143,8 @@ var ReactDOMInput = {
143 if (value != null) {
144 // Cast `value` to a string to ensure the value is set correctly. While
145 // browsers typically do this as necessary, jsdom doesn't.
145 - ReactDOMIDOperations.updatePropertyByID(
146 - inst._rootNodeID,
146 + DOMPropertyOperations.setValueForProperty(
147 + ReactDOMComponentTree.getNodeFromInstance(inst),
148 'value',
149 '' + value
150 );
@@ -163,7 +164,7 @@ function _handleChange(event) {
164
165 var name = props.name;
166 if (props.type === 'radio' && name != null) {
166 - var rootNode = ReactMount.getNode(this._rootNodeID);
167 + var rootNode = ReactDOMComponentTree.getNodeFromInstance(this);
168 var queryRoot = rootNode;
169
170 while (queryRoot.parentNode) {
src/renderers/dom/client/wrappers/ReactDOMSelect.js
+2 -2
@@ -12,7 +12,7 @@
12 'use strict';
13
14 var LinkedValueUtils = require('LinkedValueUtils');
15 -var ReactMount = require('ReactMount');
15 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
16 var ReactUpdates = require('ReactUpdates');
17
18 var assign = require('Object.assign');
@@ -112,7 +112,7 @@ function checkSelectPropTypes(inst, props) {
112 */
113 function updateOptions(inst, multiple, propValue) {
114 var selectedValue, i;
115 - var options = ReactMount.getNode(inst._rootNodeID).options;
115 + var options = ReactDOMComponentTree.getNodeFromInstance(inst).options;
116
117 if (multiple) {
118 selectedValue = {};
src/renderers/dom/client/wrappers/ReactDOMTextarea.js
+4 -3
@@ -11,8 +11,9 @@
11
12 'use strict';
13
14 +var DOMPropertyOperations = require('DOMPropertyOperations');
15 var LinkedValueUtils = require('LinkedValueUtils');
15 -var ReactDOMIDOperations = require('ReactDOMIDOperations');
16 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
17 var ReactUpdates = require('ReactUpdates');
18
19 var assign = require('Object.assign');
@@ -144,8 +145,8 @@ var ReactDOMTextarea = {
145 if (value != null) {
146 // Cast `value` to a string to ensure the value is set correctly. While
147 // browsers typically do this as necessary, jsdom doesn't.
147 - ReactDOMIDOperations.updatePropertyByID(
148 - inst._rootNodeID,
148 + DOMPropertyOperations.setValueForProperty(
149 + ReactDOMComponentTree.getNodeFromInstance(inst),
150 'value',
151 '' + value
152 );
src/renderers/dom/shared/ReactDOMComponent.js
+21 -18
@@ -27,6 +27,8 @@ var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
27 var ReactComponentBrowserEnvironment =
28 require('ReactComponentBrowserEnvironment');
29 var ReactDOMButton = require('ReactDOMButton');
30 +var ReactDOMComponentFlags = require('ReactDOMComponentFlags');
31 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
32 var ReactDOMInput = require('ReactDOMInput');
33 var ReactDOMOption = require('ReactDOMOption');
34 var ReactDOMSelect = require('ReactDOMSelect');
@@ -46,7 +48,9 @@ var shallowEqual = require('shallowEqual');
48 var validateDOMNesting = require('validateDOMNesting');
49 var warning = require('warning');
50
51 +var Flags = ReactDOMComponentFlags;
52 var deleteListener = EventPluginHub.deleteListener;
53 +var getNode = ReactDOMComponentTree.getNodeFromInstance;
54 var listenTo = ReactBrowserEventEmitter.listenTo;
55 var registrationNameModules = EventPluginRegistry.registrationNameModules;
56
@@ -493,14 +497,6 @@ function isCustomComponent(tagName, props) {
497 return tagName.indexOf('-') >= 0 || props.is != null;
498 }
499
496 -function getNode(inst) {
497 - if (inst._nativeNode) {
498 - return inst._nativeNode;
499 - } else {
500 - return inst._nativeNode = ReactMount.getNode(inst._rootNodeID);
501 - }
502 -}
503 -
500 /**
501 * Creates a new React class that is idempotent and capable of containing other
502 * React components. It accepts event listeners and DOM properties that are
@@ -523,11 +519,12 @@ function ReactDOMComponent(tag) {
519 this._previousStyle = null;
520 this._previousStyleCopy = null;
521 this._nativeNode = null;
522 + this._nativeParent = null;
523 this._rootNodeID = null;
524 this._nativeContainerInfo = null;
525 this._wrapperState = null;
526 this._topLevelWrapper = null;
530 - this._nodeHasLegacyProperties = false;
527 + this._flags = 0;
528 if (__DEV__) {
529 this._ancestorInfo = null;
530 }
@@ -561,6 +558,7 @@ ReactDOMComponent.Mixin = {
558 context
559 ) {
560 this._rootNodeID = rootID;
561 + this._nativeParent = nativeParent;
562 this._nativeContainerInfo = nativeContainerInfo;
563
564 var props = this._currentElement.props;
@@ -664,6 +662,7 @@ ReactDOMComponent.Mixin = {
662 );
663 }
664 this._nativeNode = el;
665 + this._flags |= Flags.hasCachedChildNodes;
666 DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
667 // Populate node cache
668 ReactMount.getID(el);
@@ -907,7 +906,7 @@ ReactDOMComponent.Mixin = {
906 context
907 );
908
910 - if (!canDefineProperty && this._nodeHasLegacyProperties) {
909 + if (!canDefineProperty && this._flags & Flags.nodeHasLegacyProperties) {
910 this._nativeNode.props = nextProps;
911 }
912
@@ -1146,7 +1145,7 @@ ReactDOMComponent.Mixin = {
1145 break;
1146 }
1147
1149 - if (this._nodeHasLegacyProperties) {
1148 + if (this._flags & Flags.nodeHasLegacyProperties) {
1149 this._nativeNode._reactInternalComponent = null;
1150 }
1151 this._nativeNode = null;
@@ -1159,7 +1158,7 @@ ReactDOMComponent.Mixin = {
1158 },
1159
1160 getPublicInstance: function() {
1162 - if (this._nodeHasLegacyProperties) {
1161 + if (this._flags & Flags.nodeHasLegacyProperties) {
1162 return this._nativeNode;
1163 } else {
1164 var node = getNode(this);
@@ -1185,7 +1184,7 @@ ReactDOMComponent.Mixin = {
1184 node.props = this._currentElement.props;
1185 }
1186
1188 - this._nodeHasLegacyProperties = true;
1187 + this._flags |= Flags.nodeHasLegacyProperties;
1188 return node;
1189 }
1190 },
@@ -1200,11 +1199,15 @@ ReactPerf.measureMethods(ReactDOMComponent, 'ReactDOMComponent', {
1199 assign(
1200 ReactDOMComponent.prototype,
1201 ReactDOMComponent.Mixin,
1203 - ReactMultiChild.Mixin
1202 + ReactMultiChild.Mixin,
1203 + {
1204 + prepareToManageChildren: function() {
1205 + // Before we add, remove, or reorder the children of a node, make sure
1206 + // we have references to all of its children so we don't lose them, even
1207 + // if nefarious browser plugins add extra nodes to our tree.
1208 + ReactDOMComponentTree.precacheChildNodes(this, getNode(this));
1209 + },
1210 + }
1211 );
1212
1206 -assign(ReactDOMComponent, {
1207 - getNodeFromInstance: getNode,
1208 -});
1209 -
1213 module.exports = ReactDOMComponent;
src/renderers/dom/shared/ReactDOMComponentFlags.js new
+19
@@ -0,0 +1,19 @@
1 +/**
2 + * Copyright 2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactDOMComponentFlags
10 + */
11 +
12 +'use strict';
13 +
14 +var ReactDOMComponentFlags = {
15 + nodeHasLegacyProperties: 1 << 0,
16 + hasCachedChildNodes: 1 << 1,
17 +};
18 +
19 +module.exports = ReactDOMComponentFlags;
src/renderers/dom/shared/ReactDOMTextComponent.js
+5 -7
@@ -17,19 +17,14 @@ var DOMLazyTree = require('DOMLazyTree');
17 var DOMPropertyOperations = require('DOMPropertyOperations');
18 var ReactComponentBrowserEnvironment =
19 require('ReactComponentBrowserEnvironment');
20 +var ReactDOMComponentTree = require('ReactDOMComponentTree');
21 var ReactMount = require('ReactMount');
22
23 var assign = require('Object.assign');
24 var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
25 var validateDOMNesting = require('validateDOMNesting');
26
26 -function getNode(inst) {
27 - if (inst._nativeNode) {
28 - return inst._nativeNode;
29 - } else {
30 - return inst._nativeNode = ReactMount.getNode(inst._rootNodeID);
31 - }
32 -}
27 +var getNode = ReactDOMComponentTree.getNodeFromInstance;
28
29 /**
30 * Text nodes violate a couple assumptions that React makes about components:
@@ -61,6 +56,8 @@ assign(ReactDOMTextComponent.prototype, {
56 this._currentElement = text;
57 this._stringText = '' + text;
58 this._nativeNode = null;
59 + // ReactDOMComponentTree uses this:
60 + this._nativeParent = null;
61
62 // Properties
63 this._rootNodeID = null;
@@ -98,6 +95,7 @@ assign(ReactDOMTextComponent.prototype, {
95 }
96
97 this._rootNodeID = rootID;
98 + this._nativeParent = nativeParent;
99 if (transaction.useCreateElement) {
100 var ownerDocument = nativeContainerInfo._ownerDocument;
101 var el = ownerDocument.createElement('span');
src/renderers/shared/reconciler/ReactMultiChild.js
+32 -21
@@ -51,15 +51,15 @@ var markupQueue = [];
51 /**
52 * Enqueues markup to be rendered and inserted at a supplied index.
53 *
54 - * @param {string} parentID ID of the parent component.
54 + * @param {object} parentInst parent component.
55 * @param {string} markup Markup that renders into an element.
56 * @param {number} toIndex Destination index.
57 * @private
58 */
59 -function enqueueInsertMarkup(parentID, markup, toIndex) {
59 +function enqueueInsertMarkup(parentInst, markup, toIndex) {
60 // NOTE: Null values reduce hidden classes.
61 updateQueue.push({
62 - parentID: parentID,
62 + parentInst: parentInst,
63 parentNode: null,
64 type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
65 markupIndex: markupQueue.push(markup) - 1,
@@ -72,15 +72,15 @@ function enqueueInsertMarkup(parentID, markup, toIndex) {
72 /**
73 * Enqueues moving an existing element to another index.
74 *
75 - * @param {string} parentID ID of the parent component.
75 + * @param {object} parentInst parent component.
76 * @param {number} fromIndex Source index of the existing element.
77 * @param {number} toIndex Destination index of the element.
78 * @private
79 */
80 -function enqueueMove(parentID, fromIndex, toIndex) {
80 +function enqueueMove(parentInst, fromIndex, toIndex) {
81 // NOTE: Null values reduce hidden classes.
82 updateQueue.push({
83 - parentID: parentID,
83 + parentInst: parentInst,
84 parentNode: null,
85 type: ReactMultiChildUpdateTypes.MOVE_EXISTING,
86 markupIndex: null,
@@ -93,14 +93,14 @@ function enqueueMove(parentID, fromIndex, toIndex) {
93 /**
94 * Enqueues removing an element at an index.
95 *
96 - * @param {string} parentID ID of the parent component.
96 + * @param {object} parentInst parent component.
97 * @param {number} fromIndex Index of the element to remove.
98 * @private
99 */
100 -function enqueueRemove(parentID, fromIndex) {
100 +function enqueueRemove(parentInst, fromIndex) {
101 // NOTE: Null values reduce hidden classes.
102 updateQueue.push({
103 - parentID: parentID,
103 + parentInst: parentInst,
104 parentNode: null,
105 type: ReactMultiChildUpdateTypes.REMOVE_NODE,
106 markupIndex: null,
@@ -113,14 +113,14 @@ function enqueueRemove(parentID, fromIndex) {
113 /**
114 * Enqueues setting the markup of a node.
115 *
116 - * @param {string} parentID ID of the parent component.
116 + * @param {object} parentInst parent component.
117 * @param {string} markup Markup that renders into an element.
118 * @private
119 */
120 -function enqueueSetMarkup(parentID, markup) {
120 +function enqueueSetMarkup(parentInst, markup) {
121 // NOTE: Null values reduce hidden classes.
122 updateQueue.push({
123 - parentID: parentID,
123 + parentInst: parentInst,
124 parentNode: null,
125 type: ReactMultiChildUpdateTypes.SET_MARKUP,
126 markupIndex: null,
@@ -133,14 +133,14 @@ function enqueueSetMarkup(parentID, markup) {
133 /**
134 * Enqueues setting the text content.
135 *
136 - * @param {string} parentID ID of the parent component.
136 + * @param {object} parentInst parent component.
137 * @param {string} textContent Text content to set.
138 * @private
139 */
140 -function enqueueTextContent(parentID, textContent) {
140 +function enqueueTextContent(parentInst, textContent) {
141 // NOTE: Null values reduce hidden classes.
142 updateQueue.push({
143 - parentID: parentID,
143 + parentInst: parentInst,
144 parentNode: null,
145 type: ReactMultiChildUpdateTypes.TEXT_CONTENT,
146 markupIndex: null,
@@ -371,7 +371,6 @@ var ReactMultiChild = {
371 var nextChildren = this._reconcilerUpdateChildren(
372 prevChildren, nextNestedChildrenElements, transaction, context
373 );
374 - this._renderedChildren = nextChildren;
374 if (!nextChildren && !prevChildren) {
375 return;
376 }
@@ -410,6 +409,7 @@ var ReactMultiChild = {
409 this._unmountChild(prevChildren[name]);
410 }
411 }
412 + this._renderedChildren = nextChildren;
413 },
414
415 /**
@@ -424,6 +424,14 @@ var ReactMultiChild = {
424 this._renderedChildren = null;
425 },
426
427 + /**
428 + * Hook used by the DOM implementation to precache the nodes before we apply
429 + * any reorders here.
430 + */
431 + prepareToManageChildren: function() {
432 + // TODO: This sucks. Figure out a better design here.
433 + },
434 +
435 /**
436 * Moves a child component to the supplied index.
437 *
@@ -437,7 +445,8 @@ var ReactMultiChild = {
445 // be moved. Otherwise, we do not need to move it because a child will be
446 // inserted or moved before `child`.
447 if (child._mountIndex < lastIndex) {
440 - enqueueMove(this._rootNodeID, child._mountIndex, toIndex);
448 + this.prepareToManageChildren();
449 + enqueueMove(this, child._mountIndex, toIndex);
450 }
451 },
452
@@ -449,7 +458,8 @@ var ReactMultiChild = {
458 * @protected
459 */
460 createChild: function(child, mountImage) {
452 - enqueueInsertMarkup(this._rootNodeID, mountImage, child._mountIndex);
461 + this.prepareToManageChildren();
462 + enqueueInsertMarkup(this, mountImage, child._mountIndex);
463 },
464
465 /**
@@ -459,7 +469,8 @@ var ReactMultiChild = {
469 * @protected
470 */
471 removeChild: function(child) {
462 - enqueueRemove(this._rootNodeID, child._mountIndex);
472 + this.prepareToManageChildren();
473 + enqueueRemove(this, child._mountIndex);
474 },
475
476 /**
@@ -469,7 +480,7 @@ var ReactMultiChild = {
480 * @protected
481 */
482 setTextContent: function(textContent) {
472 - enqueueTextContent(this._rootNodeID, textContent);
483 + enqueueTextContent(this, textContent);
484 },
485
486 /**
@@ -479,7 +490,7 @@ var ReactMultiChild = {
490 * @protected
491 */
492 setMarkup: function(markup) {
482 - enqueueSetMarkup(this._rootNodeID, markup);
493 + enqueueSetMarkup(this, markup);
494 },
495
496 /**
src/test/ReactDefaultPerf.js
+1 -1
@@ -194,7 +194,7 @@ var ReactDefaultPerf = {
194 writeArgs.markup = args[1][update.markupIndex];
195 }
196 ReactDefaultPerf._recordWrite(
197 - update.parentID,
197 + update.parentInst._rootNodeID,
198 update.type,
199 totalTime,
200 writeArgs