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

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="&gt;" ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
71 + '<img data-attr="&gt;" ' + 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 = '&quot;&gt;&gt;&gt; LULZ &lt;&lt;&lt;&quot;';
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