mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Use incrementing numerical IDs to identify DOM components
This commit is contained in:
1 parent
f06ddf7905
commit
e48c8be556
14 files changed
+136
-245
No files matched your search
@@ -77,7 +77,7 @@ function precacheChildNodes(inst, node) {
|
||||
continue;
|
||||
}
|
||||
var childInst = children[name];
|
||||
var childID = childInst._rootNodeID;
|
||||
var childID = getRenderedNativeOrTextFromComponent(childInst)._domID;
|
||||
if (childID == null) {
|
||||
// We're currently unmounting this child in ReactMultiChild; skip it.
|
||||
continue;
|
||||
@@ -85,7 +85,7 @@ function precacheChildNodes(inst, node) {
|
||||
// We assume the child nodes are in the same order as the child instances.
|
||||
for (; childNode !== null; childNode = childNode.nextSibling) {
|
||||
if (childNode.nodeType === 1 &&
|
||||
childNode.getAttribute(ATTR_NAME) === childID) {
|
||||
childNode.getAttribute(ATTR_NAME) === String(childID)) {
|
||||
precacheNode(childInst, childNode);
|
||||
continue outer;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ClientReactRootIndex = require('ClientReactRootIndex');
|
||||
var DOMLazyTree = require('DOMLazyTree');
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
@@ -20,7 +19,6 @@ var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
|
||||
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactReconciler = require('ReactReconciler');
|
||||
@@ -35,23 +33,12 @@ var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
var warning = require('warning');
|
||||
|
||||
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
|
||||
var ROOT_ATTR_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME;
|
||||
|
||||
var ELEMENT_NODE_TYPE = 1;
|
||||
var DOC_NODE_TYPE = 9;
|
||||
var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
|
||||
|
||||
|
||||
/** Mapping from reactRootID to React component instance. */
|
||||
var instancesByReactRootID = {};
|
||||
|
||||
/** Mapping from reactRootID to `container` nodes. */
|
||||
var containersByReactRootID = {};
|
||||
|
||||
if (__DEV__) {
|
||||
/** __DEV__-only mapping from reactRootID to root elements. */
|
||||
var rootElementsByReactRootID = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the index of the first character
|
||||
* that's not common between the two given strings.
|
||||
@@ -85,20 +72,11 @@ function getReactRootElementInContainer(container) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DOMElement} container DOM element that may contain a React component.
|
||||
* @return {?string} A "reactRoot" ID, if a React component is rendered.
|
||||
*/
|
||||
function getReactRootID(container) {
|
||||
var rootElement = getReactRootElementInContainer(container);
|
||||
return rootElement && internalGetID(rootElement);
|
||||
}
|
||||
|
||||
function internalGetID(node) {
|
||||
// If node is something like a window, document, or text node, none of
|
||||
// which support attributes or a .getAttribute method, gracefully return
|
||||
// the empty string, as if the attribute were missing.
|
||||
return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
|
||||
return node.getAttribute && node.getAttribute(ATTR_NAME) || '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +89,7 @@ function internalGetID(node) {
|
||||
* @param {boolean} shouldReuseMarkup If true, do not insert markup
|
||||
*/
|
||||
function mountComponentIntoNode(
|
||||
componentInstance,
|
||||
wrapperInstance,
|
||||
rootID,
|
||||
container,
|
||||
transaction,
|
||||
@@ -119,18 +97,18 @@ function mountComponentIntoNode(
|
||||
context
|
||||
) {
|
||||
var markup = ReactReconciler.mountComponent(
|
||||
componentInstance,
|
||||
wrapperInstance,
|
||||
rootID,
|
||||
transaction,
|
||||
null,
|
||||
ReactDOMContainerInfo(container),
|
||||
ReactDOMContainerInfo(wrapperInstance, container),
|
||||
context
|
||||
);
|
||||
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
|
||||
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
|
||||
ReactMount._mountImageIntoNode(
|
||||
markup,
|
||||
container,
|
||||
componentInstance,
|
||||
wrapperInstance,
|
||||
shouldReuseMarkup,
|
||||
transaction
|
||||
);
|
||||
@@ -200,10 +178,21 @@ function unmountComponentFromNode(instance, container) {
|
||||
* rendered by React but is not a root element.
|
||||
* @internal
|
||||
*/
|
||||
function hasNonRootReactChild(node) {
|
||||
var reactRootID = getReactRootID(node);
|
||||
return reactRootID ? reactRootID !==
|
||||
ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false;
|
||||
function hasNonRootReactChild(container) {
|
||||
var rootElement = getReactRootElementInContainer(container);
|
||||
return !!(
|
||||
rootElement &&
|
||||
internalGetID(rootElement) &&
|
||||
!rootElement.hasAttribute(ROOT_ATTR_NAME)
|
||||
);
|
||||
}
|
||||
|
||||
function getTopLevelWrapperInContainer(container) {
|
||||
var rootEl = getReactRootElementInContainer(container);
|
||||
var prevNativeInstance =
|
||||
rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl);
|
||||
return !prevNativeInstance || prevNativeInstance._nativeParent ? null :
|
||||
prevNativeInstance._nativeContainerInfo._topLevelWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,9 +232,6 @@ var ReactMount = {
|
||||
|
||||
TopLevelWrapper: TopLevelWrapper,
|
||||
|
||||
/** Exposed for debugging purposes **/
|
||||
_instancesByReactRootID: instancesByReactRootID,
|
||||
|
||||
/**
|
||||
* This is a hook provided to support rendering React components while
|
||||
* ensuring that the apparent scroll position of its `container` does not
|
||||
@@ -277,39 +263,9 @@ var ReactMount = {
|
||||
}
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
// Record the root element in case it later gets transplanted.
|
||||
rootElementsByReactRootID[getReactRootID(container)] =
|
||||
getReactRootElementInContainer(container);
|
||||
}
|
||||
|
||||
return prevComponent;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a component into the instance map and starts scroll value
|
||||
* monitoring
|
||||
* @param {ReactComponent} nextComponent component instance to render
|
||||
* @param {DOMElement} container container to render into
|
||||
* @return {string} reactRoot ID prefix
|
||||
*/
|
||||
_registerComponent: function(nextComponent, container) {
|
||||
invariant(
|
||||
container && (
|
||||
container.nodeType === ELEMENT_NODE_TYPE ||
|
||||
container.nodeType === DOC_NODE_TYPE ||
|
||||
container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE
|
||||
),
|
||||
'_registerComponent(...): Target container is not a DOM element.'
|
||||
);
|
||||
|
||||
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
|
||||
|
||||
var reactRootID = ReactMount.registerContainer(container);
|
||||
instancesByReactRootID[reactRootID] = nextComponent;
|
||||
return reactRootID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Render a new component into the DOM.
|
||||
* @param {ReactElement} nextElement element to render
|
||||
@@ -336,12 +292,18 @@ var ReactMount = {
|
||||
'ReactCompositeComponent'
|
||||
);
|
||||
|
||||
var componentInstance = instantiateReactComponent(nextElement, null);
|
||||
var reactRootID = ReactMount._registerComponent(
|
||||
componentInstance,
|
||||
container
|
||||
invariant(
|
||||
container && (
|
||||
container.nodeType === ELEMENT_NODE_TYPE ||
|
||||
container.nodeType === DOC_NODE_TYPE ||
|
||||
container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE
|
||||
),
|
||||
'_registerComponent(...): Target container is not a DOM element.'
|
||||
);
|
||||
|
||||
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
|
||||
var componentInstance = instantiateReactComponent(nextElement, null);
|
||||
|
||||
// The initial render is synchronous but any updates that happen during
|
||||
// rendering, in componentWillMount or componentDidMount, will be batched
|
||||
// according to the current batching strategy.
|
||||
@@ -349,18 +311,12 @@ var ReactMount = {
|
||||
ReactUpdates.batchedUpdates(
|
||||
batchedMountComponentIntoNode,
|
||||
componentInstance,
|
||||
reactRootID,
|
||||
'',
|
||||
container,
|
||||
shouldReuseMarkup,
|
||||
context
|
||||
);
|
||||
|
||||
if (__DEV__) {
|
||||
// Record the root element in case it later gets transplanted.
|
||||
rootElementsByReactRootID[reactRootID] =
|
||||
getReactRootElementInContainer(container);
|
||||
}
|
||||
|
||||
return componentInstance;
|
||||
},
|
||||
|
||||
@@ -429,7 +385,7 @@ var ReactMount = {
|
||||
nextElement
|
||||
);
|
||||
|
||||
var prevComponent = instancesByReactRootID[getReactRootID(container)];
|
||||
var prevComponent = getTopLevelWrapperInContainer(container);
|
||||
|
||||
if (prevComponent) {
|
||||
var prevWrappedElement = prevComponent._currentElement;
|
||||
@@ -519,27 +475,6 @@ var ReactMount = {
|
||||
return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a container node into which React components will be rendered.
|
||||
* This also creates the "reactRoot" ID that will be assigned to the element
|
||||
* rendered within.
|
||||
*
|
||||
* @param {DOMElement} container DOM element to register as a container.
|
||||
* @return {string} The "reactRoot" ID of elements rendered within.
|
||||
*/
|
||||
registerContainer: function(container) {
|
||||
var id = getReactRootID(container);
|
||||
// If one exists, make sure it is a valid "reactRoot" ID.
|
||||
if (!id || id !== ReactInstanceHandles.getReactRootIDFromNodeID(id)) {
|
||||
// No valid "reactRoot" ID found, create one.
|
||||
id = ReactInstanceHandles.createReactRootID(
|
||||
ClientReactRootIndex.createReactRootIndex()
|
||||
);
|
||||
}
|
||||
containersByReactRootID[id] = container;
|
||||
return id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unmounts and destroys the React component rendered in the `container`.
|
||||
*
|
||||
@@ -571,19 +506,15 @@ var ReactMount = {
|
||||
'unmountComponentAtNode(...): Target container is not a DOM element.'
|
||||
);
|
||||
|
||||
var reactRootID = getReactRootID(container);
|
||||
var component = instancesByReactRootID[reactRootID];
|
||||
if (!component) {
|
||||
var prevComponent = getTopLevelWrapperInContainer(container);
|
||||
if (!prevComponent) {
|
||||
// Check if the node being unmounted was rendered by React, but isn't a
|
||||
// root node.
|
||||
var containerHasNonRootReactChild = hasNonRootReactChild(container);
|
||||
|
||||
// Check if the container itself is a React root node.
|
||||
var containerID = internalGetID(container);
|
||||
var isContainerReactRoot =
|
||||
containerID &&
|
||||
containerID ===
|
||||
ReactInstanceHandles.getReactRootIDFromNodeID(containerID);
|
||||
container.nodeType === 1 && container.hasAttribute(ROOT_ATTR_NAME);
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
@@ -604,14 +535,9 @@ var ReactMount = {
|
||||
}
|
||||
ReactUpdates.batchedUpdates(
|
||||
unmountComponentFromNode,
|
||||
component,
|
||||
prevComponent,
|
||||
container
|
||||
);
|
||||
delete instancesByReactRootID[reactRootID];
|
||||
delete containersByReactRootID[reactRootID];
|
||||
if (__DEV__) {
|
||||
delete rootElementsByReactRootID[reactRootID];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
@@ -33,14 +33,6 @@ describe('rendering React components at document', function() {
|
||||
beforeEach(function() {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
// Negative integer creator. So they won't get confused with
|
||||
// the Client positive ids.
|
||||
var ServerReactRootIndex = require('ServerReactRootIndex');
|
||||
var serverId = -1;
|
||||
ServerReactRootIndex.createReactRootIndex.mockImplementation(function() {
|
||||
return serverId--;
|
||||
});
|
||||
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMServer = require('ReactDOMServer');
|
||||
@@ -221,8 +213,8 @@ describe('rendering React components at document', function() {
|
||||
'quirks by rendering at the document root. You should look for ' +
|
||||
'environment dependent code in your components and ensure ' +
|
||||
'the props are the same client and server side:\n' +
|
||||
' (client) ata-reactid=".-1.1">Hello world</body></\n' +
|
||||
' (server) ata-reactid=".-1.1">Goodbye world</body>'
|
||||
' (client) dy data-reactid="4">Hello world</body></\n' +
|
||||
' (server) dy data-reactid="4">Goodbye world</body>'
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -11,15 +11,14 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
|
||||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactInstanceHandles = require('ReactInstanceHandles');
|
||||
var ReactMarkupChecksum = require('ReactMarkupChecksum');
|
||||
var ReactServerBatchingStrategy = require('ReactServerBatchingStrategy');
|
||||
var ReactServerRenderingTransaction =
|
||||
require('ReactServerRenderingTransaction');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
var ServerReactRootIndex = require('ServerReactRootIndex');
|
||||
|
||||
var emptyObject = require('emptyObject');
|
||||
var instantiateReactComponent = require('instantiateReactComponent');
|
||||
@@ -39,18 +38,15 @@ function renderToStringImpl(element, makeStaticMarkup) {
|
||||
try {
|
||||
ReactUpdates.injection.injectBatchingStrategy(ReactServerBatchingStrategy);
|
||||
|
||||
var id = ReactInstanceHandles.createReactRootID(
|
||||
ServerReactRootIndex.createReactRootIndex()
|
||||
);
|
||||
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);
|
||||
|
||||
return transaction.perform(function() {
|
||||
var componentInstance = instantiateReactComponent(element, null);
|
||||
var markup = componentInstance.mountComponent(
|
||||
id,
|
||||
'',
|
||||
transaction,
|
||||
null,
|
||||
null,
|
||||
ReactDOMContainerInfo(),
|
||||
emptyObject
|
||||
);
|
||||
if (!makeStaticMarkup) {
|
||||
|
||||
@@ -20,6 +20,7 @@ var ReactTestUtils;
|
||||
var ReactServerRendering;
|
||||
|
||||
var ID_ATTRIBUTE_NAME;
|
||||
var ROOT_ATTRIBUTE_NAME;
|
||||
|
||||
describe('ReactServerRendering', function() {
|
||||
beforeEach(function() {
|
||||
@@ -36,7 +37,7 @@ describe('ReactServerRendering', function() {
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
|
||||
spyOn(console, 'error');
|
||||
ROOT_ATTRIBUTE_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME;
|
||||
});
|
||||
|
||||
describe('renderToString', function() {
|
||||
@@ -45,7 +46,8 @@ describe('ReactServerRendering', function() {
|
||||
<span>hello world</span>
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
'<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
|
||||
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
|
||||
);
|
||||
});
|
||||
@@ -55,7 +57,8 @@ describe('ReactServerRendering', function() {
|
||||
<img />
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<img ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
'<img ' + ROOT_ATTRIBUTE_NAME + '="" ' +
|
||||
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>'
|
||||
);
|
||||
});
|
||||
@@ -65,7 +68,8 @@ describe('ReactServerRendering', function() {
|
||||
<img data-attr=">" />
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<img data-attr=">" ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
'<img data-attr=">" ' + ROOT_ATTRIBUTE_NAME + '="" ' +
|
||||
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>'
|
||||
);
|
||||
});
|
||||
@@ -95,7 +99,8 @@ describe('ReactServerRendering', function() {
|
||||
<Parent />
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
'<div ' + ROOT_ATTRIBUTE_NAME + '="" ' +
|
||||
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
|
||||
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
|
||||
@@ -145,7 +150,8 @@ describe('ReactServerRendering', function() {
|
||||
);
|
||||
|
||||
expect(response).toMatch(
|
||||
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
'<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
|
||||
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
|
||||
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
|
||||
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
|
||||
@@ -225,6 +231,7 @@ describe('ReactServerRendering', function() {
|
||||
// Now simulate a situation where the app is not idempotent. React should
|
||||
// warn but do the right thing.
|
||||
element.innerHTML = lastMarkup;
|
||||
spyOn(console, 'error');
|
||||
var instance = ReactDOM.render(<TestComponent name="y" />, element);
|
||||
expect(mountCount).toEqual(4);
|
||||
expect(console.error.argsForCall.length).toBe(1);
|
||||
|
||||
@@ -167,6 +167,7 @@ var defaultValueCache = {};
|
||||
var DOMProperty = {
|
||||
|
||||
ID_ATTRIBUTE_NAME: 'data-reactid',
|
||||
ROOT_ATTRIBUTE_NAME: 'data-reactroot',
|
||||
|
||||
/**
|
||||
* Map from property "standard name" to an object with info about how to set
|
||||
|
||||
@@ -125,6 +125,14 @@ var DOMPropertyOperations = {
|
||||
node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id);
|
||||
},
|
||||
|
||||
createMarkupForRoot: function() {
|
||||
return DOMProperty.ROOT_ATTRIBUTE_NAME + '=""';
|
||||
},
|
||||
|
||||
setAttributeForRoot: function(node) {
|
||||
node.setAttribute(DOMProperty.ROOT_ATTRIBUTE_NAME, '');
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates markup for a property.
|
||||
*
|
||||
|
||||
@@ -301,11 +301,11 @@ function enqueuePutListener(inst, registrationName, listener, transaction) {
|
||||
);
|
||||
}
|
||||
var containerInfo = inst._nativeContainerInfo;
|
||||
if (!containerInfo) {
|
||||
var doc = containerInfo._ownerDocument;
|
||||
if (!doc) {
|
||||
// Server rendering.
|
||||
return;
|
||||
}
|
||||
var doc = containerInfo._ownerDocument;
|
||||
listenTo(registrationName, doc);
|
||||
transaction.getReactMountReady().enqueue(putListener, {
|
||||
inst: inst,
|
||||
@@ -494,6 +494,8 @@ function isCustomComponent(tagName, props) {
|
||||
return tagName.indexOf('-') >= 0 || props.is != null;
|
||||
}
|
||||
|
||||
var globalIdCounter = 1;
|
||||
|
||||
/**
|
||||
* Creates a new React class that is idempotent and capable of containing other
|
||||
* React components. It accepts event listeners and DOM properties that are
|
||||
@@ -518,6 +520,7 @@ function ReactDOMComponent(tag) {
|
||||
this._nativeNode = null;
|
||||
this._nativeParent = null;
|
||||
this._rootNodeID = null;
|
||||
this._domID = null;
|
||||
this._nativeContainerInfo = null;
|
||||
this._wrapperState = null;
|
||||
this._topLevelWrapper = null;
|
||||
@@ -554,7 +557,8 @@ ReactDOMComponent.Mixin = {
|
||||
nativeContainerInfo,
|
||||
context
|
||||
) {
|
||||
this._rootNodeID = rootID;
|
||||
this._rootNodeID = globalIdCounter++;
|
||||
this._domID = nativeContainerInfo._idCounter++;
|
||||
this._nativeParent = nativeParent;
|
||||
this._nativeContainerInfo = nativeContainerInfo;
|
||||
|
||||
@@ -604,7 +608,7 @@ ReactDOMComponent.Mixin = {
|
||||
if (nativeParent != null) {
|
||||
namespaceURI = nativeParent._namespaceURI;
|
||||
parentTag = nativeParent._tag;
|
||||
} else if (nativeContainerInfo != null) {
|
||||
} else if (nativeContainerInfo._tag) {
|
||||
namespaceURI = nativeContainerInfo._namespaceURI;
|
||||
parentTag = nativeContainerInfo._tag;
|
||||
}
|
||||
@@ -625,7 +629,7 @@ ReactDOMComponent.Mixin = {
|
||||
var parentInfo;
|
||||
if (nativeParent != null) {
|
||||
parentInfo = nativeParent._ancestorInfo;
|
||||
} else if (nativeContainerInfo != null) {
|
||||
} else if (nativeContainerInfo._tag) {
|
||||
parentInfo = nativeContainerInfo._ancestorInfo;
|
||||
}
|
||||
if (parentInfo) {
|
||||
@@ -660,7 +664,10 @@ ReactDOMComponent.Mixin = {
|
||||
}
|
||||
ReactDOMComponentTree.precacheNode(this, el);
|
||||
this._flags |= Flags.hasCachedChildNodes;
|
||||
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
|
||||
if (!this._nativeParent) {
|
||||
DOMPropertyOperations.setAttributeForRoot(el);
|
||||
}
|
||||
DOMPropertyOperations.setAttributeForID(el, this._domID);
|
||||
this._updateDOMProperties(null, props, transaction);
|
||||
var lazyTree = DOMLazyTree(el);
|
||||
this._createInitialChildren(transaction, props, context, lazyTree);
|
||||
@@ -757,8 +764,11 @@ ReactDOMComponent.Mixin = {
|
||||
return ret;
|
||||
}
|
||||
|
||||
var markupForID = DOMPropertyOperations.createMarkupForID(this._rootNodeID);
|
||||
return ret + ' ' + markupForID;
|
||||
if (!this._nativeParent) {
|
||||
ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
|
||||
}
|
||||
ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1145,6 +1155,7 @@ ReactDOMComponent.Mixin = {
|
||||
EventPluginHub.deleteAllListeners(this);
|
||||
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
|
||||
this._rootNodeID = null;
|
||||
this._domID = null;
|
||||
this._wrapperState = null;
|
||||
},
|
||||
|
||||
|
||||
@@ -15,15 +15,19 @@ var validateDOMNesting = require('validateDOMNesting');
|
||||
|
||||
var DOC_NODE_TYPE = 9;
|
||||
|
||||
function ReactDOMContainerInfo(node) {
|
||||
function ReactDOMContainerInfo(topLevelWrapper, node) {
|
||||
var info = {
|
||||
_ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
|
||||
_tag: node.nodeName.toLowerCase(),
|
||||
_namespaceURI: node.namespaceURI,
|
||||
_topLevelWrapper: topLevelWrapper,
|
||||
_idCounter: 1,
|
||||
_ownerDocument: node ?
|
||||
node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument :
|
||||
null,
|
||||
_tag: node ? node.nodeName.toLowerCase() : null,
|
||||
_namespaceURI: node ? node.namespaceURI : null,
|
||||
};
|
||||
if (__DEV__) {
|
||||
info._ancestorInfo =
|
||||
validateDOMNesting.updatedAncestorInfo(null, info._tag, null);
|
||||
info._ancestorInfo = node ?
|
||||
validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
var DOMChildrenOperations = require('DOMChildrenOperations');
|
||||
var DOMLazyTree = require('DOMLazyTree');
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactComponentBrowserEnvironment =
|
||||
require('ReactComponentBrowserEnvironment');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
@@ -60,7 +58,7 @@ assign(ReactDOMTextComponent.prototype, {
|
||||
this._nativeParent = null;
|
||||
|
||||
// Properties
|
||||
this._rootNodeID = null;
|
||||
this._domID = null;
|
||||
this._mountIndex = 0;
|
||||
},
|
||||
|
||||
@@ -94,13 +92,14 @@ assign(ReactDOMTextComponent.prototype, {
|
||||
}
|
||||
}
|
||||
|
||||
this._rootNodeID = rootID;
|
||||
var domID = nativeContainerInfo._idCounter++;
|
||||
this._domID = domID;
|
||||
this._nativeParent = nativeParent;
|
||||
if (transaction.useCreateElement) {
|
||||
var ownerDocument = nativeContainerInfo._ownerDocument;
|
||||
var el = ownerDocument.createElement('span');
|
||||
ReactDOMComponentTree.precacheNode(this, el);
|
||||
DOMPropertyOperations.setAttributeForID(el, rootID);
|
||||
DOMPropertyOperations.setAttributeForID(el, domID);
|
||||
var lazyTree = DOMLazyTree(el);
|
||||
DOMLazyTree.queueText(lazyTree, this._stringText);
|
||||
return lazyTree;
|
||||
@@ -115,7 +114,7 @@ assign(ReactDOMTextComponent.prototype, {
|
||||
}
|
||||
|
||||
return (
|
||||
'<span ' + DOMPropertyOperations.createMarkupForID(rootID) + '>' +
|
||||
'<span ' + DOMPropertyOperations.createMarkupForID(domID) + '>' +
|
||||
escapedText +
|
||||
'</span>'
|
||||
);
|
||||
@@ -148,8 +147,7 @@ assign(ReactDOMTextComponent.prototype, {
|
||||
},
|
||||
|
||||
unmountComponent: function() {
|
||||
this._nativeNode = null;
|
||||
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
|
||||
ReactDOMComponentTree.uncacheNode(this);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -13,22 +13,11 @@
|
||||
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactDOMServer;
|
||||
|
||||
describe('ReactDOMTextComponent', function() {
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMServer = require('ReactDOMServer');
|
||||
});
|
||||
|
||||
it('should escape the rootID', function() {
|
||||
var ThisThingShouldBeEscaped = '">>> LULZ <<<"';
|
||||
var ThisThingWasBeEscaped = '">>> LULZ <<<"';
|
||||
var thing = <div><span key={ThisThingShouldBeEscaped}>LULZ</span></div>;
|
||||
var html = ReactDOMServer.renderToString(thing);
|
||||
expect(html).not.toContain(ThisThingShouldBeEscaped);
|
||||
expect(html).toContain(ThisThingWasBeEscaped);
|
||||
});
|
||||
|
||||
it('updates a mounted text component in place', function() {
|
||||
|
||||
@@ -249,11 +249,9 @@ var ReactMultiChild = {
|
||||
for (var name in children) {
|
||||
if (children.hasOwnProperty(name)) {
|
||||
var child = children[name];
|
||||
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
|
||||
var rootID = this._rootNodeID + name;
|
||||
var mountImage = ReactReconciler.mountComponent(
|
||||
child,
|
||||
rootID,
|
||||
name,
|
||||
transaction,
|
||||
this,
|
||||
this._nativeContainerInfo,
|
||||
@@ -510,11 +508,9 @@ var ReactMultiChild = {
|
||||
index,
|
||||
transaction,
|
||||
context) {
|
||||
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
|
||||
var rootID = this._rootNodeID + name;
|
||||
var mountImage = ReactReconciler.mountComponent(
|
||||
child,
|
||||
rootID,
|
||||
name,
|
||||
transaction,
|
||||
this,
|
||||
this._nativeContainerInfo,
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactDOMComponentTree;
|
||||
var ReactFragment;
|
||||
var ReactTestUtils;
|
||||
|
||||
@@ -23,85 +22,52 @@ describe('ReactIdentity', function() {
|
||||
jest.resetModuleRegistry();
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
ReactFragment = require('ReactFragment');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
});
|
||||
|
||||
var idExp = /^\.[^.]+(.*)$/;
|
||||
function checkID(child, expectedID) {
|
||||
// TODO: Node IDs are not public API; rewrite these tests.
|
||||
var rootID = ReactDOMComponentTree.getInstanceFromNode(child)._rootNodeID;
|
||||
var actual = idExp.exec(rootID);
|
||||
var expected = idExp.exec(expectedID);
|
||||
expect(actual).toBeTruthy();
|
||||
expect(expected).toBeTruthy();
|
||||
expect(actual[1]).toEqual(expected[1]);
|
||||
}
|
||||
|
||||
function frag(obj) {
|
||||
return ReactFragment.create(obj);
|
||||
}
|
||||
|
||||
it('should allow keyed objects to express identity', function() {
|
||||
var instance =
|
||||
<div>
|
||||
{frag({
|
||||
first: <div />,
|
||||
second: <div />,
|
||||
})}
|
||||
</div>;
|
||||
|
||||
instance = ReactDOM.render(instance, document.createElement('div'));
|
||||
var node = ReactDOM.findDOMNode(instance);
|
||||
expect(node.childNodes.length).toBe(2);
|
||||
checkID(node.childNodes[0], '.0.$first/=10');
|
||||
checkID(node.childNodes[1], '.0.$second/=10');
|
||||
});
|
||||
|
||||
it('should allow key property to express identity', function() {
|
||||
var instance =
|
||||
<div>
|
||||
<div key="apple" />
|
||||
<div key="banana" />
|
||||
<div key={0} />
|
||||
<div key={123} />
|
||||
var node;
|
||||
var Component = (props) =>
|
||||
<div ref={(c) => node = c}>
|
||||
<div key={props.swap ? 'banana' : 'apple'} />
|
||||
<div key={props.swap ? 'apple' : 'banana'} />
|
||||
</div>;
|
||||
|
||||
instance = ReactDOM.render(instance, document.createElement('div'));
|
||||
var node = ReactDOM.findDOMNode(instance);
|
||||
expect(node.childNodes.length).toBe(4);
|
||||
checkID(node.childNodes[0], '.0.$apple');
|
||||
checkID(node.childNodes[1], '.0.$banana');
|
||||
checkID(node.childNodes[2], '.0.$0');
|
||||
checkID(node.childNodes[3], '.0.$123');
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component />, container);
|
||||
var origChildren = Array.from(node.childNodes);
|
||||
ReactDOM.render(<Component swap={true} />, container);
|
||||
var newChildren = Array.from(node.childNodes);
|
||||
expect(origChildren[0]).toBe(newChildren[1]);
|
||||
expect(origChildren[1]).toBe(newChildren[0]);
|
||||
});
|
||||
|
||||
it('should use instance identity', function() {
|
||||
it('should use composite identity', function() {
|
||||
|
||||
var Wrapper = React.createClass({
|
||||
render: function() {
|
||||
return <a key="i_get_overwritten">{this.props.children}</a>;
|
||||
return <a>{this.props.children}</a>;
|
||||
},
|
||||
});
|
||||
|
||||
var instance =
|
||||
<div>
|
||||
<Wrapper key="wrap1"><span key="squirrel" /></Wrapper>
|
||||
<Wrapper key="wrap2"><span key="bunny" /></Wrapper>
|
||||
<Wrapper><span key="chipmunk" /></Wrapper>
|
||||
</div>;
|
||||
var container = document.createElement('div');
|
||||
var node1;
|
||||
var node2;
|
||||
ReactDOM.render(
|
||||
<Wrapper key="wrap1"><span ref={(c) => node1 = c} /></Wrapper>,
|
||||
container
|
||||
);
|
||||
ReactDOM.render(
|
||||
<Wrapper key="wrap2"><span ref={(c) => node2 = c} /></Wrapper>,
|
||||
container
|
||||
);
|
||||
|
||||
instance = ReactDOM.render(instance, document.createElement('div'));
|
||||
var node = ReactDOM.findDOMNode(instance);
|
||||
expect(node.childNodes.length).toBe(3);
|
||||
|
||||
checkID(node.childNodes[0], '.0.$wrap1');
|
||||
checkID(node.childNodes[0].firstChild, '.0.$wrap1.$squirrel');
|
||||
checkID(node.childNodes[1], '.0.$wrap2');
|
||||
checkID(node.childNodes[1].firstChild, '.0.$wrap2.$bunny');
|
||||
checkID(node.childNodes[2], '.0.2');
|
||||
checkID(node.childNodes[2].firstChild, '.0.2.$chipmunk');
|
||||
expect(node1).not.toBe(node2);
|
||||
});
|
||||
|
||||
function renderAComponentWithKeyIntoContainer(key, container) {
|
||||
@@ -125,11 +91,6 @@ describe('ReactIdentity', function() {
|
||||
|
||||
expect(ReactDOM.findDOMNode(span1)).not.toBe(null);
|
||||
expect(ReactDOM.findDOMNode(span2)).not.toBe(null);
|
||||
|
||||
key = key.replace(/=/g, '=0');
|
||||
checkID(ReactDOM.findDOMNode(span1), '.0.$' + key);
|
||||
key = key.replace(/\//g, '//');
|
||||
checkID(ReactDOM.findDOMNode(span2), '.0.1:$' + key + '/=10');
|
||||
}
|
||||
|
||||
it('should allow any character as a key, in a detached parent', function() {
|
||||
|
||||
@@ -122,7 +122,7 @@ var FriendsStatusDisplay = React.createClass({
|
||||
});
|
||||
|
||||
|
||||
function getInteralStateByUserName(statusDisplays) {
|
||||
function getInternalStateByUserName(statusDisplays) {
|
||||
return mapObject(statusDisplays, function(statusDisplay, key) {
|
||||
return statusDisplay.getInternalState();
|
||||
});
|
||||
@@ -201,7 +201,9 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
|
||||
continue;
|
||||
}
|
||||
var statusDisplay = statusDisplays[username];
|
||||
orderedLogicalIDs.push(ReactInstanceMap.get(statusDisplay)._rootNodeID);
|
||||
orderedLogicalIDs.push(
|
||||
ReactInstanceMap.get(statusDisplay)._renderedComponent._rootNodeID
|
||||
);
|
||||
}
|
||||
expect(orderedDomIDs).toEqual(orderedLogicalIDs);
|
||||
}
|
||||
@@ -217,7 +219,7 @@ function testPropsSequence(sequence) {
|
||||
container
|
||||
);
|
||||
var statusDisplays = parentInstance.getStatusDisplays();
|
||||
var lastInternalStates = getInteralStateByUserName(statusDisplays);
|
||||
var lastInternalStates = getInternalStateByUserName(statusDisplays);
|
||||
verifyStatuses(statusDisplays, sequence[0]);
|
||||
|
||||
for (i = 1; i < sequence.length; i++) {
|
||||
@@ -230,7 +232,7 @@ function testPropsSequence(sequence) {
|
||||
verifyStatesPreserved(lastInternalStates, statusDisplays);
|
||||
verifyDomOrderingAccurate(parentInstance, statusDisplays);
|
||||
|
||||
lastInternalStates = getInteralStateByUserName(statusDisplays);
|
||||
lastInternalStates = getInternalStateByUserName(statusDisplays);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user