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