Use incrementing numerical IDs to identify DOM components
Ben Alpert committed
Oct 17, 2015 at 23:05 UTC
e48c8be556fd0ef8edee76a1d0c0c686880d9c65
14 files changed
+136
-245
src/renderers/dom/client/ReactDOMComponentTree.js
+2
-2
@@ -77,7 +77,7 @@ function precacheChildNodes(inst, node) {
77
continue;
78
}
79
var childInst = children[name];
80
- var childID = childInst._rootNodeID;
80
+ var childID = getRenderedNativeOrTextFromComponent(childInst)._domID;
81
if (childID == null) {
82
// We're currently unmounting this child in ReactMultiChild; skip it.
83
continue;
@@ -85,7 +85,7 @@ function precacheChildNodes(inst, node) {
85
// We assume the child nodes are in the same order as the child instances.
86
for (; childNode !== null; childNode = childNode.nextSibling) {
87
if (childNode.nodeType === 1 &&
88
- childNode.getAttribute(ATTR_NAME) === childID) {
88
+ childNode.getAttribute(ATTR_NAME) === String(childID)) {
89
precacheNode(childInst, childNode);
90
continue outer;
91
}
src/renderers/dom/client/ReactMount.js
+38
-112
@@ -11,7 +11,6 @@
11
12
'use strict';
13
14
-var ClientReactRootIndex = require('ClientReactRootIndex');
14
var DOMLazyTree = require('DOMLazyTree');
15
var DOMProperty = require('DOMProperty');
16
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
@@ -20,7 +19,6 @@ var ReactDOMComponentTree = require('ReactDOMComponentTree');
19
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
20
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
21
var ReactElement = require('ReactElement');
23
-var ReactInstanceHandles = require('ReactInstanceHandles');
22
var ReactMarkupChecksum = require('ReactMarkupChecksum');
23
var ReactPerf = require('ReactPerf');
24
var ReactReconciler = require('ReactReconciler');
@@ -35,23 +33,12 @@ var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
33
var warning = require('warning');
34
35
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
36
+var ROOT_ATTR_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME;
37
38
var ELEMENT_NODE_TYPE = 1;
39
var DOC_NODE_TYPE = 9;
40
var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
41
43
-
44
-/** Mapping from reactRootID to React component instance. */
45
-var instancesByReactRootID = {};
46
-
47
-/** Mapping from reactRootID to `container` nodes. */
48
-var containersByReactRootID = {};
49
-
50
-if (__DEV__) {
51
- /** __DEV__-only mapping from reactRootID to root elements. */
52
- var rootElementsByReactRootID = {};
53
-}
54
-
42
/**
43
* Finds the index of the first character
44
* that's not common between the two given strings.
@@ -85,20 +72,11 @@ function getReactRootElementInContainer(container) {
72
}
73
}
74
88
-/**
89
- * @param {DOMElement} container DOM element that may contain a React component.
90
- * @return {?string} A "reactRoot" ID, if a React component is rendered.
91
- */
92
-function getReactRootID(container) {
93
- var rootElement = getReactRootElementInContainer(container);
94
- return rootElement && internalGetID(rootElement);
95
-}
96
-
75
function internalGetID(node) {
76
// If node is something like a window, document, or text node, none of
77
// which support attributes or a .getAttribute method, gracefully return
78
// the empty string, as if the attribute were missing.
101
- return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
79
+ return node.getAttribute && node.getAttribute(ATTR_NAME) || '';
80
}
81
82
/**
@@ -111,7 +89,7 @@ function internalGetID(node) {
89
* @param {boolean} shouldReuseMarkup If true, do not insert markup
90
*/
91
function mountComponentIntoNode(
114
- componentInstance,
92
+ wrapperInstance,
93
rootID,
94
container,
95
transaction,
@@ -119,18 +97,18 @@ function mountComponentIntoNode(
97
context
98
) {
99
var markup = ReactReconciler.mountComponent(
122
- componentInstance,
100
+ wrapperInstance,
101
rootID,
102
transaction,
103
null,
126
- ReactDOMContainerInfo(container),
104
+ ReactDOMContainerInfo(wrapperInstance, container),
105
context
106
);
129
- componentInstance._renderedComponent._topLevelWrapper = componentInstance;
107
+ wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
108
ReactMount._mountImageIntoNode(
109
markup,
110
container,
133
- componentInstance,
111
+ wrapperInstance,
112
shouldReuseMarkup,
113
transaction
114
);
@@ -200,10 +178,21 @@ function unmountComponentFromNode(instance, container) {
178
* rendered by React but is not a root element.
179
* @internal
180
*/
203
-function hasNonRootReactChild(node) {
204
- var reactRootID = getReactRootID(node);
205
- return reactRootID ? reactRootID !==
206
- ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false;
181
+function hasNonRootReactChild(container) {
182
+ var rootElement = getReactRootElementInContainer(container);
183
+ return !!(
184
+ rootElement &&
185
+ internalGetID(rootElement) &&
186
+ !rootElement.hasAttribute(ROOT_ATTR_NAME)
187
+ );
188
+}
189
+
190
+function getTopLevelWrapperInContainer(container) {
191
+ var rootEl = getReactRootElementInContainer(container);
192
+ var prevNativeInstance =
193
+ rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl);
194
+ return !prevNativeInstance || prevNativeInstance._nativeParent ? null :
195
+ prevNativeInstance._nativeContainerInfo._topLevelWrapper;
196
}
197
198
/**
@@ -243,9 +232,6 @@ var ReactMount = {
232
233
TopLevelWrapper: TopLevelWrapper,
234
246
- /** Exposed for debugging purposes **/
247
- _instancesByReactRootID: instancesByReactRootID,
248
-
235
/**
236
* This is a hook provided to support rendering React components while
237
* ensuring that the apparent scroll position of its `container` does not
@@ -277,39 +263,9 @@ var ReactMount = {
263
}
264
});
265
280
- if (__DEV__) {
281
- // Record the root element in case it later gets transplanted.
282
- rootElementsByReactRootID[getReactRootID(container)] =
283
- getReactRootElementInContainer(container);
284
- }
285
-
266
return prevComponent;
267
},
268
289
- /**
290
- * Register a component into the instance map and starts scroll value
291
- * monitoring
292
- * @param {ReactComponent} nextComponent component instance to render
293
- * @param {DOMElement} container container to render into
294
- * @return {string} reactRoot ID prefix
295
- */
296
- _registerComponent: function(nextComponent, container) {
297
- invariant(
298
- container && (
299
- container.nodeType === ELEMENT_NODE_TYPE ||
300
- container.nodeType === DOC_NODE_TYPE ||
301
- container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE
302
- ),
303
- '_registerComponent(...): Target container is not a DOM element.'
304
- );
305
-
306
- ReactBrowserEventEmitter.ensureScrollValueMonitoring();
307
-
308
- var reactRootID = ReactMount.registerContainer(container);
309
- instancesByReactRootID[reactRootID] = nextComponent;
310
- return reactRootID;
311
- },
312
-
269
/**
270
* Render a new component into the DOM.
271
* @param {ReactElement} nextElement element to render
@@ -336,12 +292,18 @@ var ReactMount = {
292
'ReactCompositeComponent'
293
);
294
339
- var componentInstance = instantiateReactComponent(nextElement, null);
340
- var reactRootID = ReactMount._registerComponent(
341
- componentInstance,
342
- container
295
+ invariant(
296
+ container && (
297
+ container.nodeType === ELEMENT_NODE_TYPE ||
298
+ container.nodeType === DOC_NODE_TYPE ||
299
+ container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE
300
+ ),
301
+ '_registerComponent(...): Target container is not a DOM element.'
302
);
303
304
+ ReactBrowserEventEmitter.ensureScrollValueMonitoring();
305
+ var componentInstance = instantiateReactComponent(nextElement, null);
306
+
307
// The initial render is synchronous but any updates that happen during
308
// rendering, in componentWillMount or componentDidMount, will be batched
309
// according to the current batching strategy.
@@ -349,18 +311,12 @@ var ReactMount = {
311
ReactUpdates.batchedUpdates(
312
batchedMountComponentIntoNode,
313
componentInstance,
352
- reactRootID,
314
+ '',
315
container,
316
shouldReuseMarkup,
317
context
318
);
319
358
- if (__DEV__) {
359
- // Record the root element in case it later gets transplanted.
360
- rootElementsByReactRootID[reactRootID] =
361
- getReactRootElementInContainer(container);
362
- }
363
-
320
return componentInstance;
321
},
322
@@ -429,7 +385,7 @@ var ReactMount = {
385
nextElement
386
);
387
432
- var prevComponent = instancesByReactRootID[getReactRootID(container)];
388
+ var prevComponent = getTopLevelWrapperInContainer(container);
389
390
if (prevComponent) {
391
var prevWrappedElement = prevComponent._currentElement;
@@ -519,27 +475,6 @@ var ReactMount = {
475
return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
476
},
477
522
- /**
523
- * Registers a container node into which React components will be rendered.
524
- * This also creates the "reactRoot" ID that will be assigned to the element
525
- * rendered within.
526
- *
527
- * @param {DOMElement} container DOM element to register as a container.
528
- * @return {string} The "reactRoot" ID of elements rendered within.
529
- */
530
- registerContainer: function(container) {
531
- var id = getReactRootID(container);
532
- // If one exists, make sure it is a valid "reactRoot" ID.
533
- if (!id || id !== ReactInstanceHandles.getReactRootIDFromNodeID(id)) {
534
- // No valid "reactRoot" ID found, create one.
535
- id = ReactInstanceHandles.createReactRootID(
536
- ClientReactRootIndex.createReactRootIndex()
537
- );
538
- }
539
- containersByReactRootID[id] = container;
540
- return id;
541
- },
542
-
478
/**
479
* Unmounts and destroys the React component rendered in the `container`.
480
*
@@ -571,19 +506,15 @@ var ReactMount = {
506
'unmountComponentAtNode(...): Target container is not a DOM element.'
507
);
508
574
- var reactRootID = getReactRootID(container);
575
- var component = instancesByReactRootID[reactRootID];
576
- if (!component) {
509
+ var prevComponent = getTopLevelWrapperInContainer(container);
510
+ if (!prevComponent) {
511
// Check if the node being unmounted was rendered by React, but isn't a
512
// root node.
513
var containerHasNonRootReactChild = hasNonRootReactChild(container);
514
515
// Check if the container itself is a React root node.
582
- var containerID = internalGetID(container);
516
var isContainerReactRoot =
584
- containerID &&
585
- containerID ===
586
- ReactInstanceHandles.getReactRootIDFromNodeID(containerID);
517
+ container.nodeType === 1 && container.hasAttribute(ROOT_ATTR_NAME);
518
519
if (__DEV__) {
520
warning(
@@ -604,14 +535,9 @@ var ReactMount = {
535
}
536
ReactUpdates.batchedUpdates(
537
unmountComponentFromNode,
607
- component,
538
+ prevComponent,
539
container
540
);
610
- delete instancesByReactRootID[reactRootID];
611
- delete containersByReactRootID[reactRootID];
612
- if (__DEV__) {
613
- delete rootElementsByReactRootID[reactRootID];
614
- }
541
return true;
542
},
543
src/renderers/dom/client/__tests__/ReactRenderDocument-test.js
+2
-10
@@ -33,14 +33,6 @@ describe('rendering React components at document', function() {
33
beforeEach(function() {
34
jest.resetModuleRegistry();
35
36
- // Negative integer creator. So they won't get confused with
37
- // the Client positive ids.
38
- var ServerReactRootIndex = require('ServerReactRootIndex');
39
- var serverId = -1;
40
- ServerReactRootIndex.createReactRootIndex.mockImplementation(function() {
41
- return serverId--;
42
- });
43
-
36
React = require('React');
37
ReactDOM = require('ReactDOM');
38
ReactDOMServer = require('ReactDOMServer');
@@ -221,8 +213,8 @@ describe('rendering React components at document', function() {
213
'quirks by rendering at the document root. You should look for ' +
214
'environment dependent code in your components and ensure ' +
215
'the props are the same client and server side:\n' +
224
- ' (client) ata-reactid=".-1.1">Hello world</body></\n' +
225
- ' (server) ata-reactid=".-1.1">Goodbye world</body>'
216
+ ' (client) dy data-reactid="4">Hello world</body></\n' +
217
+ ' (server) dy data-reactid="4">Goodbye world</body>'
218
);
219
});
220
src/renderers/dom/server/ReactServerRendering.js
+3
-7
@@ -11,15 +11,14 @@
11
*/
12
'use strict';
13
14
+var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
15
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
16
var ReactElement = require('ReactElement');
16
-var ReactInstanceHandles = require('ReactInstanceHandles');
17
var ReactMarkupChecksum = require('ReactMarkupChecksum');
18
var ReactServerBatchingStrategy = require('ReactServerBatchingStrategy');
19
var ReactServerRenderingTransaction =
20
require('ReactServerRenderingTransaction');
21
var ReactUpdates = require('ReactUpdates');
22
-var ServerReactRootIndex = require('ServerReactRootIndex');
22
23
var emptyObject = require('emptyObject');
24
var instantiateReactComponent = require('instantiateReactComponent');
@@ -39,18 +38,15 @@ function renderToStringImpl(element, makeStaticMarkup) {
38
try {
39
ReactUpdates.injection.injectBatchingStrategy(ReactServerBatchingStrategy);
40
42
- var id = ReactInstanceHandles.createReactRootID(
43
- ServerReactRootIndex.createReactRootIndex()
44
- );
41
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);
42
43
return transaction.perform(function() {
44
var componentInstance = instantiateReactComponent(element, null);
45
var markup = componentInstance.mountComponent(
50
- id,
46
+ '',
47
transaction,
48
null,
53
- null,
49
+ ReactDOMContainerInfo(),
50
emptyObject
51
);
52
if (!makeStaticMarkup) {
src/renderers/dom/server/__tests__/ReactServerRendering-test.js
+13
-6
@@ -20,6 +20,7 @@ var ReactTestUtils;
20
var ReactServerRendering;
21
22
var ID_ATTRIBUTE_NAME;
23
+var ROOT_ATTRIBUTE_NAME;
24
25
describe('ReactServerRendering', function() {
26
beforeEach(function() {
@@ -36,7 +37,7 @@ describe('ReactServerRendering', function() {
37
38
var DOMProperty = require('DOMProperty');
39
ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
39
- spyOn(console, 'error');
40
+ ROOT_ATTRIBUTE_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME;
41
});
42
43
describe('renderToString', function() {
@@ -45,7 +46,8 @@ describe('ReactServerRendering', function() {
46
<span>hello world</span>
47
);
48
expect(response).toMatch(
48
- '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
49
+ '<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
50
+ ID_ATTRIBUTE_NAME + '="[^"]+" ' +
51
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
52
);
53
});
@@ -55,7 +57,8 @@ describe('ReactServerRendering', function() {
57
<img />
58
);
59
expect(response).toMatch(
58
- '<img ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
60
+ '<img ' + ROOT_ATTRIBUTE_NAME + '="" ' +
61
+ ID_ATTRIBUTE_NAME + '="[^"]+" ' +
62
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>'
63
);
64
});
@@ -65,7 +68,8 @@ describe('ReactServerRendering', function() {
68
<img data-attr=">" />
69
);
70
expect(response).toMatch(
68
- '<img data-attr=">" ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
71
+ '<img data-attr=">" ' + ROOT_ATTRIBUTE_NAME + '="" ' +
72
+ ID_ATTRIBUTE_NAME + '="[^"]+" ' +
73
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>'
74
);
75
});
@@ -95,7 +99,8 @@ describe('ReactServerRendering', function() {
99
<Parent />
100
);
101
expect(response).toMatch(
98
- '<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
102
+ '<div ' + ROOT_ATTRIBUTE_NAME + '="" ' +
103
+ ID_ATTRIBUTE_NAME + '="[^"]+" ' +
104
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
105
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
106
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
@@ -145,7 +150,8 @@ describe('ReactServerRendering', function() {
150
);
151
152
expect(response).toMatch(
148
- '<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
153
+ '<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
154
+ ID_ATTRIBUTE_NAME + '="[^"]+" ' +
155
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
156
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
157
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
@@ -225,6 +231,7 @@ describe('ReactServerRendering', function() {
231
// Now simulate a situation where the app is not idempotent. React should
232
// warn but do the right thing.
233
element.innerHTML = lastMarkup;
234
+ spyOn(console, 'error');
235
var instance = ReactDOM.render(<TestComponent name="y" />, element);
236
expect(mountCount).toEqual(4);
237
expect(console.error.argsForCall.length).toBe(1);
src/renderers/dom/shared/DOMProperty.js
+1
@@ -167,6 +167,7 @@ var defaultValueCache = {};
167
var DOMProperty = {
168
169
ID_ATTRIBUTE_NAME: 'data-reactid',
170
+ ROOT_ATTRIBUTE_NAME: 'data-reactroot',
171
172
/**
173
* Map from property "standard name" to an object with info about how to set
src/renderers/dom/shared/DOMPropertyOperations.js
+8
@@ -125,6 +125,14 @@ var DOMPropertyOperations = {
125
node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id);
126
},
127
128
+ createMarkupForRoot: function() {
129
+ return DOMProperty.ROOT_ATTRIBUTE_NAME + '=""';
130
+ },
131
+
132
+ setAttributeForRoot: function(node) {
133
+ node.setAttribute(DOMProperty.ROOT_ATTRIBUTE_NAME, '');
134
+ },
135
+
136
/**
137
* Creates markup for a property.
138
*
src/renderers/dom/shared/ReactDOMComponent.js
+19
-8
@@ -301,11 +301,11 @@ function enqueuePutListener(inst, registrationName, listener, transaction) {
301
);
302
}
303
var containerInfo = inst._nativeContainerInfo;
304
- if (!containerInfo) {
304
+ var doc = containerInfo._ownerDocument;
305
+ if (!doc) {
306
// Server rendering.
307
return;
308
}
308
- var doc = containerInfo._ownerDocument;
309
listenTo(registrationName, doc);
310
transaction.getReactMountReady().enqueue(putListener, {
311
inst: inst,
@@ -494,6 +494,8 @@ function isCustomComponent(tagName, props) {
494
return tagName.indexOf('-') >= 0 || props.is != null;
495
}
496
497
+var globalIdCounter = 1;
498
+
499
/**
500
* Creates a new React class that is idempotent and capable of containing other
501
* React components. It accepts event listeners and DOM properties that are
@@ -518,6 +520,7 @@ function ReactDOMComponent(tag) {
520
this._nativeNode = null;
521
this._nativeParent = null;
522
this._rootNodeID = null;
523
+ this._domID = null;
524
this._nativeContainerInfo = null;
525
this._wrapperState = null;
526
this._topLevelWrapper = null;
@@ -554,7 +557,8 @@ ReactDOMComponent.Mixin = {
557
nativeContainerInfo,
558
context
559
) {
557
- this._rootNodeID = rootID;
560
+ this._rootNodeID = globalIdCounter++;
561
+ this._domID = nativeContainerInfo._idCounter++;
562
this._nativeParent = nativeParent;
563
this._nativeContainerInfo = nativeContainerInfo;
564
@@ -604,7 +608,7 @@ ReactDOMComponent.Mixin = {
608
if (nativeParent != null) {
609
namespaceURI = nativeParent._namespaceURI;
610
parentTag = nativeParent._tag;
607
- } else if (nativeContainerInfo != null) {
611
+ } else if (nativeContainerInfo._tag) {
612
namespaceURI = nativeContainerInfo._namespaceURI;
613
parentTag = nativeContainerInfo._tag;
614
}
@@ -625,7 +629,7 @@ ReactDOMComponent.Mixin = {
629
var parentInfo;
630
if (nativeParent != null) {
631
parentInfo = nativeParent._ancestorInfo;
628
- } else if (nativeContainerInfo != null) {
632
+ } else if (nativeContainerInfo._tag) {
633
parentInfo = nativeContainerInfo._ancestorInfo;
634
}
635
if (parentInfo) {
@@ -660,7 +664,10 @@ ReactDOMComponent.Mixin = {
664
}
665
ReactDOMComponentTree.precacheNode(this, el);
666
this._flags |= Flags.hasCachedChildNodes;
663
- DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
667
+ if (!this._nativeParent) {
668
+ DOMPropertyOperations.setAttributeForRoot(el);
669
+ }
670
+ DOMPropertyOperations.setAttributeForID(el, this._domID);
671
this._updateDOMProperties(null, props, transaction);
672
var lazyTree = DOMLazyTree(el);
673
this._createInitialChildren(transaction, props, context, lazyTree);
@@ -757,8 +764,11 @@ ReactDOMComponent.Mixin = {
764
return ret;
765
}
766
760
- var markupForID = DOMPropertyOperations.createMarkupForID(this._rootNodeID);
761
- return ret + ' ' + markupForID;
767
+ if (!this._nativeParent) {
768
+ ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
769
+ }
770
+ ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
771
+ return ret;
772
},
773
774
/**
@@ -1145,6 +1155,7 @@ ReactDOMComponent.Mixin = {
1155
EventPluginHub.deleteAllListeners(this);
1156
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
1157
this._rootNodeID = null;
1158
+ this._domID = null;
1159
this._wrapperState = null;
1160
},
1161
src/renderers/dom/shared/ReactDOMContainerInfo.js
+10
-6
@@ -15,15 +15,19 @@ var validateDOMNesting = require('validateDOMNesting');
15
16
var DOC_NODE_TYPE = 9;
17
18
-function ReactDOMContainerInfo(node) {
18
+function ReactDOMContainerInfo(topLevelWrapper, node) {
19
var info = {
20
- _ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
21
- _tag: node.nodeName.toLowerCase(),
22
- _namespaceURI: node.namespaceURI,
20
+ _topLevelWrapper: topLevelWrapper,
21
+ _idCounter: 1,
22
+ _ownerDocument: node ?
23
+ node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument :
24
+ null,
25
+ _tag: node ? node.nodeName.toLowerCase() : null,
26
+ _namespaceURI: node ? node.namespaceURI : null,
27
};
28
if (__DEV__) {
25
- info._ancestorInfo =
26
- validateDOMNesting.updatedAncestorInfo(null, info._tag, null);
29
+ info._ancestorInfo = node ?
30
+ validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null;
31
}
32
return info;
33
}
src/renderers/dom/shared/ReactDOMTextComponent.js
+6
-8
@@ -15,8 +15,6 @@
15
var DOMChildrenOperations = require('DOMChildrenOperations');
16
var DOMLazyTree = require('DOMLazyTree');
17
var DOMPropertyOperations = require('DOMPropertyOperations');
18
-var ReactComponentBrowserEnvironment =
19
- require('ReactComponentBrowserEnvironment');
18
var ReactDOMComponentTree = require('ReactDOMComponentTree');
19
var ReactPerf = require('ReactPerf');
20
@@ -60,7 +58,7 @@ assign(ReactDOMTextComponent.prototype, {
58
this._nativeParent = null;
59
60
// Properties
63
- this._rootNodeID = null;
61
+ this._domID = null;
62
this._mountIndex = 0;
63
},
64
@@ -94,13 +92,14 @@ assign(ReactDOMTextComponent.prototype, {
92
}
93
}
94
97
- this._rootNodeID = rootID;
95
+ var domID = nativeContainerInfo._idCounter++;
96
+ this._domID = domID;
97
this._nativeParent = nativeParent;
98
if (transaction.useCreateElement) {
99
var ownerDocument = nativeContainerInfo._ownerDocument;
100
var el = ownerDocument.createElement('span');
101
ReactDOMComponentTree.precacheNode(this, el);
103
- DOMPropertyOperations.setAttributeForID(el, rootID);
102
+ DOMPropertyOperations.setAttributeForID(el, domID);
103
var lazyTree = DOMLazyTree(el);
104
DOMLazyTree.queueText(lazyTree, this._stringText);
105
return lazyTree;
@@ -115,7 +114,7 @@ assign(ReactDOMTextComponent.prototype, {
114
}
115
116
return (
118
- '<span ' + DOMPropertyOperations.createMarkupForID(rootID) + '>' +
117
+ '<span ' + DOMPropertyOperations.createMarkupForID(domID) + '>' +
118
escapedText +
119
'</span>'
120
);
@@ -148,8 +147,7 @@ assign(ReactDOMTextComponent.prototype, {
147
},
148
149
unmountComponent: function() {
151
- this._nativeNode = null;
152
- ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
150
+ ReactDOMComponentTree.uncacheNode(this);
151
},
152
153
});
src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
-11
@@ -13,22 +13,11 @@
13
14
var React;
15
var ReactDOM;
16
-var ReactDOMServer;
16
17
describe('ReactDOMTextComponent', function() {
18
beforeEach(function() {
19
React = require('React');
20
ReactDOM = require('ReactDOM');
22
- ReactDOMServer = require('ReactDOMServer');
23
- });
24
-
25
- it('should escape the rootID', function() {
26
- var ThisThingShouldBeEscaped = '">>> LULZ <<<"';
27
- var ThisThingWasBeEscaped = '">>> LULZ <<<"';
28
- var thing = <div><span key={ThisThingShouldBeEscaped}>LULZ</span></div>;
29
- var html = ReactDOMServer.renderToString(thing);
30
- expect(html).not.toContain(ThisThingShouldBeEscaped);
31
- expect(html).toContain(ThisThingWasBeEscaped);
21
});
22
23
it('updates a mounted text component in place', function() {
src/renderers/shared/reconciler/ReactMultiChild.js
+2
-6
@@ -249,11 +249,9 @@ var ReactMultiChild = {
249
for (var name in children) {
250
if (children.hasOwnProperty(name)) {
251
var child = children[name];
252
- // Inlined for performance, see `ReactInstanceHandles.createReactID`.
253
- var rootID = this._rootNodeID + name;
252
var mountImage = ReactReconciler.mountComponent(
253
child,
256
- rootID,
254
+ name,
255
transaction,
256
this,
257
this._nativeContainerInfo,
@@ -510,11 +508,9 @@ var ReactMultiChild = {
508
index,
509
transaction,
510
context) {
513
- // Inlined for performance, see `ReactInstanceHandles.createReactID`.
514
- var rootID = this._rootNodeID + name;
511
var mountImage = ReactReconciler.mountComponent(
512
child,
517
- rootID,
513
+ name,
514
transaction,
515
this,
516
this._nativeContainerInfo,
src/renderers/shared/reconciler/__tests__/ReactIdentity-test.js
+26
-65
@@ -13,7 +13,6 @@
13
14
var React;
15
var ReactDOM;
16
-var ReactDOMComponentTree;
16
var ReactFragment;
17
var ReactTestUtils;
18
@@ -23,85 +22,52 @@ describe('ReactIdentity', function() {
22
jest.resetModuleRegistry();
23
React = require('React');
24
ReactDOM = require('ReactDOM');
26
- ReactDOMComponentTree = require('ReactDOMComponentTree');
25
ReactFragment = require('ReactFragment');
26
ReactTestUtils = require('ReactTestUtils');
27
});
28
31
- var idExp = /^\.[^.]+(.*)$/;
32
- function checkID(child, expectedID) {
33
- // TODO: Node IDs are not public API; rewrite these tests.
34
- var rootID = ReactDOMComponentTree.getInstanceFromNode(child)._rootNodeID;
35
- var actual = idExp.exec(rootID);
36
- var expected = idExp.exec(expectedID);
37
- expect(actual).toBeTruthy();
38
- expect(expected).toBeTruthy();
39
- expect(actual[1]).toEqual(expected[1]);
40
- }
41
-
29
function frag(obj) {
30
return ReactFragment.create(obj);
31
}
32
46
- it('should allow keyed objects to express identity', function() {
47
- var instance =
48
- <div>
49
- {frag({
50
- first: <div />,
51
- second: <div />,
52
- })}
53
- </div>;
54
-
55
- instance = ReactDOM.render(instance, document.createElement('div'));
56
- var node = ReactDOM.findDOMNode(instance);
57
- expect(node.childNodes.length).toBe(2);
58
- checkID(node.childNodes[0], '.0.$first/=10');
59
- checkID(node.childNodes[1], '.0.$second/=10');
60
- });
61
-
33
it('should allow key property to express identity', function() {
63
- var instance =
64
- <div>
65
- <div key="apple" />
66
- <div key="banana" />
67
- <div key={0} />
68
- <div key={123} />
34
+ var node;
35
+ var Component = (props) =>
36
+ <div ref={(c) => node = c}>
37
+ <div key={props.swap ? 'banana' : 'apple'} />
38
+ <div key={props.swap ? 'apple' : 'banana'} />
39
</div>;
40
71
- instance = ReactDOM.render(instance, document.createElement('div'));
72
- var node = ReactDOM.findDOMNode(instance);
73
- expect(node.childNodes.length).toBe(4);
74
- checkID(node.childNodes[0], '.0.$apple');
75
- checkID(node.childNodes[1], '.0.$banana');
76
- checkID(node.childNodes[2], '.0.$0');
77
- checkID(node.childNodes[3], '.0.$123');
41
+ var container = document.createElement('div');
42
+ ReactDOM.render(<Component />, container);
43
+ var origChildren = Array.from(node.childNodes);
44
+ ReactDOM.render(<Component swap={true} />, container);
45
+ var newChildren = Array.from(node.childNodes);
46
+ expect(origChildren[0]).toBe(newChildren[1]);
47
+ expect(origChildren[1]).toBe(newChildren[0]);
48
});
49
80
- it('should use instance identity', function() {
50
+ it('should use composite identity', function() {
51
52
var Wrapper = React.createClass({
53
render: function() {
84
- return <a key="i_get_overwritten">{this.props.children}</a>;
54
+ return <a>{this.props.children}</a>;
55
},
56
});
57
88
- var instance =
89
- <div>
90
- <Wrapper key="wrap1"><span key="squirrel" /></Wrapper>
91
- <Wrapper key="wrap2"><span key="bunny" /></Wrapper>
92
- <Wrapper><span key="chipmunk" /></Wrapper>
93
- </div>;
94
-
95
- instance = ReactDOM.render(instance, document.createElement('div'));
96
- var node = ReactDOM.findDOMNode(instance);
97
- expect(node.childNodes.length).toBe(3);
58
+ var container = document.createElement('div');
59
+ var node1;
60
+ var node2;
61
+ ReactDOM.render(
62
+ <Wrapper key="wrap1"><span ref={(c) => node1 = c} /></Wrapper>,
63
+ container
64
+ );
65
+ ReactDOM.render(
66
+ <Wrapper key="wrap2"><span ref={(c) => node2 = c} /></Wrapper>,
67
+ container
68
+ );
69
99
- checkID(node.childNodes[0], '.0.$wrap1');
100
- checkID(node.childNodes[0].firstChild, '.0.$wrap1.$squirrel');
101
- checkID(node.childNodes[1], '.0.$wrap2');
102
- checkID(node.childNodes[1].firstChild, '.0.$wrap2.$bunny');
103
- checkID(node.childNodes[2], '.0.2');
104
- checkID(node.childNodes[2].firstChild, '.0.2.$chipmunk');
70
+ expect(node1).not.toBe(node2);
71
});
72
73
function renderAComponentWithKeyIntoContainer(key, container) {
@@ -125,11 +91,6 @@ describe('ReactIdentity', function() {
91
92
expect(ReactDOM.findDOMNode(span1)).not.toBe(null);
93
expect(ReactDOM.findDOMNode(span2)).not.toBe(null);
128
-
129
- key = key.replace(/=/g, '=0');
130
- checkID(ReactDOM.findDOMNode(span1), '.0.$' + key);
131
- key = key.replace(/\//g, '//');
132
- checkID(ReactDOM.findDOMNode(span2), '.0.1:$' + key + '/=10');
94
}
95
96
it('should allow any character as a key, in a detached parent', function() {
src/renderers/shared/reconciler/__tests__/ReactMultiChildReconcile-test.js
+6
-4
@@ -122,7 +122,7 @@ var FriendsStatusDisplay = React.createClass({
122
});
123
124
125
-function getInteralStateByUserName(statusDisplays) {
125
+function getInternalStateByUserName(statusDisplays) {
126
return mapObject(statusDisplays, function(statusDisplay, key) {
127
return statusDisplay.getInternalState();
128
});
@@ -201,7 +201,9 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
201
continue;
202
}
203
var statusDisplay = statusDisplays[username];
204
- orderedLogicalIDs.push(ReactInstanceMap.get(statusDisplay)._rootNodeID);
204
+ orderedLogicalIDs.push(
205
+ ReactInstanceMap.get(statusDisplay)._renderedComponent._rootNodeID
206
+ );
207
}
208
expect(orderedDomIDs).toEqual(orderedLogicalIDs);
209
}
@@ -217,7 +219,7 @@ function testPropsSequence(sequence) {
219
container
220
);
221
var statusDisplays = parentInstance.getStatusDisplays();
220
- var lastInternalStates = getInteralStateByUserName(statusDisplays);
222
+ var lastInternalStates = getInternalStateByUserName(statusDisplays);
223
verifyStatuses(statusDisplays, sequence[0]);
224
225
for (i = 1; i < sequence.length; i++) {
@@ -230,7 +232,7 @@ function testPropsSequence(sequence) {
232
verifyStatesPreserved(lastInternalStates, statusDisplays);
233
verifyDomOrderingAccurate(parentInstance, statusDisplays);
234
233
- lastInternalStates = getInteralStateByUserName(statusDisplays);
235
+ lastInternalStates = getInternalStateByUserName(statusDisplays);
236
}
237
}
238