You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
887 lines
30 KiB
887 lines
30 KiB
/** |
|
* Copyright 2013-2015, Facebook, Inc. |
|
* All rights reserved. |
|
* |
|
* This source code is licensed under the BSD-style license found in the |
|
* LICENSE file in the root directory of this source tree. An additional grant |
|
* of patent rights can be found in the PATENTS file in the same directory. |
|
* |
|
* @providesModule ReactMount |
|
*/ |
|
|
|
'use strict'; |
|
|
|
var DOMProperty = require("./DOMProperty"); |
|
var ReactBrowserEventEmitter = require("./ReactBrowserEventEmitter"); |
|
var ReactCurrentOwner = require("./ReactCurrentOwner"); |
|
var ReactElement = require("./ReactElement"); |
|
var ReactElementValidator = require("./ReactElementValidator"); |
|
var ReactEmptyComponent = require("./ReactEmptyComponent"); |
|
var ReactInstanceHandles = require("./ReactInstanceHandles"); |
|
var ReactInstanceMap = require("./ReactInstanceMap"); |
|
var ReactMarkupChecksum = require("./ReactMarkupChecksum"); |
|
var ReactPerf = require("./ReactPerf"); |
|
var ReactReconciler = require("./ReactReconciler"); |
|
var ReactUpdateQueue = require("./ReactUpdateQueue"); |
|
var ReactUpdates = require("./ReactUpdates"); |
|
|
|
var emptyObject = require("./emptyObject"); |
|
var containsNode = require("./containsNode"); |
|
var getReactRootElementInContainer = require("./getReactRootElementInContainer"); |
|
var instantiateReactComponent = require("./instantiateReactComponent"); |
|
var invariant = require("./invariant"); |
|
var setInnerHTML = require("./setInnerHTML"); |
|
var shouldUpdateReactComponent = require("./shouldUpdateReactComponent"); |
|
var warning = require("./warning"); |
|
|
|
var SEPARATOR = ReactInstanceHandles.SEPARATOR; |
|
|
|
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME; |
|
var nodeCache = {}; |
|
|
|
var ELEMENT_NODE_TYPE = 1; |
|
var DOC_NODE_TYPE = 9; |
|
|
|
/** Mapping from reactRootID to React component instance. */ |
|
var instancesByReactRootID = {}; |
|
|
|
/** Mapping from reactRootID to `container` nodes. */ |
|
var containersByReactRootID = {}; |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
/** __DEV__-only mapping from reactRootID to root elements. */ |
|
var rootElementsByReactRootID = {}; |
|
} |
|
|
|
// Used to store breadth-first search state in findComponentRoot. |
|
var findComponentRootReusableArray = []; |
|
|
|
/** |
|
* Finds the index of the first character |
|
* that's not common between the two given strings. |
|
* |
|
* @return {number} the index of the character where the strings diverge |
|
*/ |
|
function firstDifferenceIndex(string1, string2) { |
|
var minLen = Math.min(string1.length, string2.length); |
|
for (var i = 0; i < minLen; i++) { |
|
if (string1.charAt(i) !== string2.charAt(i)) { |
|
return i; |
|
} |
|
} |
|
return string1.length === string2.length ? -1 : minLen; |
|
} |
|
|
|
/** |
|
* @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 && ReactMount.getID(rootElement); |
|
} |
|
|
|
/** |
|
* Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form |
|
* element can return its control whose name or ID equals ATTR_NAME. All |
|
* DOM nodes support `getAttributeNode` but this can also get called on |
|
* other objects so just return '' if we're given something other than a |
|
* DOM node (such as window). |
|
* |
|
* @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node. |
|
* @return {string} ID of the supplied `domNode`. |
|
*/ |
|
function getID(node) { |
|
var id = internalGetID(node); |
|
if (id) { |
|
if (nodeCache.hasOwnProperty(id)) { |
|
var cached = nodeCache[id]; |
|
if (cached !== node) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
!isValid(cached, id), |
|
'ReactMount: Two valid but unequal nodes with the same `%s`: %s', |
|
ATTR_NAME, id |
|
) : invariant(!isValid(cached, id))); |
|
|
|
nodeCache[id] = node; |
|
} |
|
} else { |
|
nodeCache[id] = node; |
|
} |
|
} |
|
|
|
return id; |
|
} |
|
|
|
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) || ''; |
|
} |
|
|
|
/** |
|
* Sets the React-specific ID of the given node. |
|
* |
|
* @param {DOMElement} node The DOM node whose ID will be set. |
|
* @param {string} id The value of the ID attribute. |
|
*/ |
|
function setID(node, id) { |
|
var oldID = internalGetID(node); |
|
if (oldID !== id) { |
|
delete nodeCache[oldID]; |
|
} |
|
node.setAttribute(ATTR_NAME, id); |
|
nodeCache[id] = node; |
|
} |
|
|
|
/** |
|
* Finds the node with the supplied React-generated DOM ID. |
|
* |
|
* @param {string} id A React-generated DOM ID. |
|
* @return {DOMElement} DOM node with the suppled `id`. |
|
* @internal |
|
*/ |
|
function getNode(id) { |
|
if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { |
|
nodeCache[id] = ReactMount.findReactNodeByID(id); |
|
} |
|
return nodeCache[id]; |
|
} |
|
|
|
/** |
|
* Finds the node with the supplied public React instance. |
|
* |
|
* @param {*} instance A public React instance. |
|
* @return {?DOMElement} DOM node with the suppled `id`. |
|
* @internal |
|
*/ |
|
function getNodeFromInstance(instance) { |
|
var id = ReactInstanceMap.get(instance)._rootNodeID; |
|
if (ReactEmptyComponent.isNullComponentID(id)) { |
|
return null; |
|
} |
|
if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) { |
|
nodeCache[id] = ReactMount.findReactNodeByID(id); |
|
} |
|
return nodeCache[id]; |
|
} |
|
|
|
/** |
|
* A node is "valid" if it is contained by a currently mounted container. |
|
* |
|
* This means that the node does not have to be contained by a document in |
|
* order to be considered valid. |
|
* |
|
* @param {?DOMElement} node The candidate DOM node. |
|
* @param {string} id The expected ID of the node. |
|
* @return {boolean} Whether the node is contained by a mounted container. |
|
*/ |
|
function isValid(node, id) { |
|
if (node) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
internalGetID(node) === id, |
|
'ReactMount: Unexpected modification of `%s`', |
|
ATTR_NAME |
|
) : invariant(internalGetID(node) === id)); |
|
|
|
var container = ReactMount.findReactContainerForID(id); |
|
if (container && containsNode(container, node)) { |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/** |
|
* Causes the cache to forget about one React-specific ID. |
|
* |
|
* @param {string} id The ID to forget. |
|
*/ |
|
function purgeID(id) { |
|
delete nodeCache[id]; |
|
} |
|
|
|
var deepestNodeSoFar = null; |
|
function findDeepestCachedAncestorImpl(ancestorID) { |
|
var ancestor = nodeCache[ancestorID]; |
|
if (ancestor && isValid(ancestor, ancestorID)) { |
|
deepestNodeSoFar = ancestor; |
|
} else { |
|
// This node isn't populated in the cache, so presumably none of its |
|
// descendants are. Break out of the loop. |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* Return the deepest cached node whose ID is a prefix of `targetID`. |
|
*/ |
|
function findDeepestCachedAncestor(targetID) { |
|
deepestNodeSoFar = null; |
|
ReactInstanceHandles.traverseAncestors( |
|
targetID, |
|
findDeepestCachedAncestorImpl |
|
); |
|
|
|
var foundNode = deepestNodeSoFar; |
|
deepestNodeSoFar = null; |
|
return foundNode; |
|
} |
|
|
|
/** |
|
* Mounts this component and inserts it into the DOM. |
|
* |
|
* @param {ReactComponent} componentInstance The instance to mount. |
|
* @param {string} rootID DOM ID of the root node. |
|
* @param {DOMElement} container DOM element to mount into. |
|
* @param {ReactReconcileTransaction} transaction |
|
* @param {boolean} shouldReuseMarkup If true, do not insert markup |
|
*/ |
|
function mountComponentIntoNode( |
|
componentInstance, |
|
rootID, |
|
container, |
|
transaction, |
|
shouldReuseMarkup) { |
|
var markup = ReactReconciler.mountComponent( |
|
componentInstance, rootID, transaction, emptyObject |
|
); |
|
componentInstance._isTopLevel = true; |
|
ReactMount._mountImageIntoNode(markup, container, shouldReuseMarkup); |
|
} |
|
|
|
/** |
|
* Batched mount. |
|
* |
|
* @param {ReactComponent} componentInstance The instance to mount. |
|
* @param {string} rootID DOM ID of the root node. |
|
* @param {DOMElement} container DOM element to mount into. |
|
* @param {boolean} shouldReuseMarkup If true, do not insert markup |
|
*/ |
|
function batchedMountComponentIntoNode( |
|
componentInstance, |
|
rootID, |
|
container, |
|
shouldReuseMarkup) { |
|
var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); |
|
transaction.perform( |
|
mountComponentIntoNode, |
|
null, |
|
componentInstance, |
|
rootID, |
|
container, |
|
transaction, |
|
shouldReuseMarkup |
|
); |
|
ReactUpdates.ReactReconcileTransaction.release(transaction); |
|
} |
|
|
|
/** |
|
* Mounting is the process of initializing a React component by creating its |
|
* representative DOM elements and inserting them into a supplied `container`. |
|
* Any prior content inside `container` is destroyed in the process. |
|
* |
|
* ReactMount.render( |
|
* component, |
|
* document.getElementById('container') |
|
* ); |
|
* |
|
* <div id="container"> <-- Supplied `container`. |
|
* <div data-reactid=".3"> <-- Rendered reactRoot of React |
|
* // ... component. |
|
* </div> |
|
* </div> |
|
* |
|
* Inside of `container`, the first element rendered is the "reactRoot". |
|
*/ |
|
var ReactMount = { |
|
/** 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 |
|
* change. |
|
* |
|
* @param {DOMElement} container The `container` being rendered into. |
|
* @param {function} renderCallback This must be called once to do the render. |
|
*/ |
|
scrollMonitor: function(container, renderCallback) { |
|
renderCallback(); |
|
}, |
|
|
|
/** |
|
* Take a component that's already mounted into the DOM and replace its props |
|
* @param {ReactComponent} prevComponent component instance already in the DOM |
|
* @param {ReactElement} nextElement component instance to render |
|
* @param {DOMElement} container container to render into |
|
* @param {?function} callback function triggered on completion |
|
*/ |
|
_updateRootComponent: function( |
|
prevComponent, |
|
nextElement, |
|
container, |
|
callback) { |
|
if ("production" !== process.env.NODE_ENV) { |
|
ReactElementValidator.checkAndWarnForMutatedProps(nextElement); |
|
} |
|
|
|
ReactMount.scrollMonitor(container, function() { |
|
ReactUpdateQueue.enqueueElementInternal(prevComponent, nextElement); |
|
if (callback) { |
|
ReactUpdateQueue.enqueueCallbackInternal(prevComponent, callback); |
|
} |
|
}); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
// 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) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
container && ( |
|
(container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) |
|
), |
|
'_registerComponent(...): Target container is not a DOM element.' |
|
) : invariant(container && ( |
|
(container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) |
|
))); |
|
|
|
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 |
|
* @param {DOMElement} container container to render into |
|
* @param {boolean} shouldReuseMarkup if we should skip the markup insertion |
|
* @return {ReactComponent} nextComponent |
|
*/ |
|
_renderNewRootComponent: function( |
|
nextElement, |
|
container, |
|
shouldReuseMarkup |
|
) { |
|
// Various parts of our code (such as ReactCompositeComponent's |
|
// _renderValidatedComponent) assume that calls to render aren't nested; |
|
// verify that that's the case. |
|
("production" !== process.env.NODE_ENV ? warning( |
|
ReactCurrentOwner.current == null, |
|
'_renderNewRootComponent(): Render methods should be a pure function ' + |
|
'of props and state; triggering nested component updates from ' + |
|
'render is not allowed. If necessary, trigger nested updates in ' + |
|
'componentDidUpdate.' |
|
) : null); |
|
|
|
var componentInstance = instantiateReactComponent(nextElement, null); |
|
var reactRootID = ReactMount._registerComponent( |
|
componentInstance, |
|
container |
|
); |
|
|
|
// 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. |
|
|
|
ReactUpdates.batchedUpdates( |
|
batchedMountComponentIntoNode, |
|
componentInstance, |
|
reactRootID, |
|
container, |
|
shouldReuseMarkup |
|
); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
// Record the root element in case it later gets transplanted. |
|
rootElementsByReactRootID[reactRootID] = |
|
getReactRootElementInContainer(container); |
|
} |
|
|
|
return componentInstance; |
|
}, |
|
|
|
/** |
|
* Renders a React component into the DOM in the supplied `container`. |
|
* |
|
* If the React component was previously rendered into `container`, this will |
|
* perform an update on it and only mutate the DOM as necessary to reflect the |
|
* latest React component. |
|
* |
|
* @param {ReactElement} nextElement Component element to render. |
|
* @param {DOMElement} container DOM element to render into. |
|
* @param {?function} callback function triggered on completion |
|
* @return {ReactComponent} Component instance rendered in `container`. |
|
*/ |
|
render: function(nextElement, container, callback) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
ReactElement.isValidElement(nextElement), |
|
'React.render(): Invalid component element.%s', |
|
( |
|
typeof nextElement === 'string' ? |
|
' Instead of passing an element string, make sure to instantiate ' + |
|
'it by passing it to React.createElement.' : |
|
typeof nextElement === 'function' ? |
|
' Instead of passing a component class, make sure to instantiate ' + |
|
'it by passing it to React.createElement.' : |
|
// Check if it quacks like an element |
|
nextElement != null && nextElement.props !== undefined ? |
|
' This may be caused by unintentionally loading two independent ' + |
|
'copies of React.' : |
|
'' |
|
) |
|
) : invariant(ReactElement.isValidElement(nextElement))); |
|
|
|
var prevComponent = instancesByReactRootID[getReactRootID(container)]; |
|
|
|
if (prevComponent) { |
|
var prevElement = prevComponent._currentElement; |
|
if (shouldUpdateReactComponent(prevElement, nextElement)) { |
|
return ReactMount._updateRootComponent( |
|
prevComponent, |
|
nextElement, |
|
container, |
|
callback |
|
).getPublicInstance(); |
|
} else { |
|
ReactMount.unmountComponentAtNode(container); |
|
} |
|
} |
|
|
|
var reactRootElement = getReactRootElementInContainer(container); |
|
var containerHasReactMarkup = |
|
reactRootElement && ReactMount.isRenderedByReact(reactRootElement); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
if (!containerHasReactMarkup || reactRootElement.nextSibling) { |
|
var rootElementSibling = reactRootElement; |
|
while (rootElementSibling) { |
|
if (ReactMount.isRenderedByReact(rootElementSibling)) { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
false, |
|
'render(): Target node has markup rendered by React, but there ' + |
|
'are unrelated nodes as well. This is most commonly caused by ' + |
|
'white-space inserted around server-rendered markup.' |
|
) : null); |
|
break; |
|
} |
|
|
|
rootElementSibling = rootElementSibling.nextSibling; |
|
} |
|
} |
|
} |
|
|
|
var shouldReuseMarkup = containerHasReactMarkup && !prevComponent; |
|
|
|
var component = ReactMount._renderNewRootComponent( |
|
nextElement, |
|
container, |
|
shouldReuseMarkup |
|
).getPublicInstance(); |
|
if (callback) { |
|
callback.call(component); |
|
} |
|
return component; |
|
}, |
|
|
|
/** |
|
* Constructs a component instance of `constructor` with `initialProps` and |
|
* renders it into the supplied `container`. |
|
* |
|
* @param {function} constructor React component constructor. |
|
* @param {?object} props Initial props of the component instance. |
|
* @param {DOMElement} container DOM element to render into. |
|
* @return {ReactComponent} Component instance rendered in `container`. |
|
*/ |
|
constructAndRenderComponent: function(constructor, props, container) { |
|
var element = ReactElement.createElement(constructor, props); |
|
return ReactMount.render(element, container); |
|
}, |
|
|
|
/** |
|
* Constructs a component instance of `constructor` with `initialProps` and |
|
* renders it into a container node identified by supplied `id`. |
|
* |
|
* @param {function} componentConstructor React component constructor |
|
* @param {?object} props Initial props of the component instance. |
|
* @param {string} id ID of the DOM element to render into. |
|
* @return {ReactComponent} Component instance rendered in the container node. |
|
*/ |
|
constructAndRenderComponentByID: function(constructor, props, id) { |
|
var domNode = document.getElementById(id); |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
domNode, |
|
'Tried to get element with id of "%s" but it is not present on the page.', |
|
id |
|
) : invariant(domNode)); |
|
return ReactMount.constructAndRenderComponent(constructor, props, domNode); |
|
}, |
|
|
|
/** |
|
* 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 reactRootID = getReactRootID(container); |
|
if (reactRootID) { |
|
// If one exists, make sure it is a valid "reactRoot" ID. |
|
reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID); |
|
} |
|
if (!reactRootID) { |
|
// No valid "reactRoot" ID found, create one. |
|
reactRootID = ReactInstanceHandles.createReactRootID(); |
|
} |
|
containersByReactRootID[reactRootID] = container; |
|
return reactRootID; |
|
}, |
|
|
|
/** |
|
* Unmounts and destroys the React component rendered in the `container`. |
|
* |
|
* @param {DOMElement} container DOM element containing a React component. |
|
* @return {boolean} True if a component was found in and unmounted from |
|
* `container` |
|
*/ |
|
unmountComponentAtNode: function(container) { |
|
// Various parts of our code (such as ReactCompositeComponent's |
|
// _renderValidatedComponent) assume that calls to render aren't nested; |
|
// verify that that's the case. (Strictly speaking, unmounting won't cause a |
|
// render but we still don't expect to be in a render call here.) |
|
("production" !== process.env.NODE_ENV ? warning( |
|
ReactCurrentOwner.current == null, |
|
'unmountComponentAtNode(): Render methods should be a pure function of ' + |
|
'props and state; triggering nested component updates from render is ' + |
|
'not allowed. If necessary, trigger nested updates in ' + |
|
'componentDidUpdate.' |
|
) : null); |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
container && ( |
|
(container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) |
|
), |
|
'unmountComponentAtNode(...): Target container is not a DOM element.' |
|
) : invariant(container && ( |
|
(container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) |
|
))); |
|
|
|
var reactRootID = getReactRootID(container); |
|
var component = instancesByReactRootID[reactRootID]; |
|
if (!component) { |
|
return false; |
|
} |
|
ReactMount.unmountComponentFromNode(component, container); |
|
delete instancesByReactRootID[reactRootID]; |
|
delete containersByReactRootID[reactRootID]; |
|
if ("production" !== process.env.NODE_ENV) { |
|
delete rootElementsByReactRootID[reactRootID]; |
|
} |
|
return true; |
|
}, |
|
|
|
/** |
|
* Unmounts a component and removes it from the DOM. |
|
* |
|
* @param {ReactComponent} instance React component instance. |
|
* @param {DOMElement} container DOM element to unmount from. |
|
* @final |
|
* @internal |
|
* @see {ReactMount.unmountComponentAtNode} |
|
*/ |
|
unmountComponentFromNode: function(instance, container) { |
|
ReactReconciler.unmountComponent(instance); |
|
|
|
if (container.nodeType === DOC_NODE_TYPE) { |
|
container = container.documentElement; |
|
} |
|
|
|
// http://jsperf.com/emptying-a-node |
|
while (container.lastChild) { |
|
container.removeChild(container.lastChild); |
|
} |
|
}, |
|
|
|
/** |
|
* Finds the container DOM element that contains React component to which the |
|
* supplied DOM `id` belongs. |
|
* |
|
* @param {string} id The ID of an element rendered by a React component. |
|
* @return {?DOMElement} DOM element that contains the `id`. |
|
*/ |
|
findReactContainerForID: function(id) { |
|
var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id); |
|
var container = containersByReactRootID[reactRootID]; |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
var rootElement = rootElementsByReactRootID[reactRootID]; |
|
if (rootElement && rootElement.parentNode !== container) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
// Call internalGetID here because getID calls isValid which calls |
|
// findReactContainerForID (this function). |
|
internalGetID(rootElement) === reactRootID, |
|
'ReactMount: Root element ID differed from reactRootID.' |
|
) : invariant(// Call internalGetID here because getID calls isValid which calls |
|
// findReactContainerForID (this function). |
|
internalGetID(rootElement) === reactRootID)); |
|
|
|
var containerChild = container.firstChild; |
|
if (containerChild && |
|
reactRootID === internalGetID(containerChild)) { |
|
// If the container has a new child with the same ID as the old |
|
// root element, then rootElementsByReactRootID[reactRootID] is |
|
// just stale and needs to be updated. The case that deserves a |
|
// warning is when the container is empty. |
|
rootElementsByReactRootID[reactRootID] = containerChild; |
|
} else { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
false, |
|
'ReactMount: Root element has been removed from its original ' + |
|
'container. New container:', rootElement.parentNode |
|
) : null); |
|
} |
|
} |
|
} |
|
|
|
return container; |
|
}, |
|
|
|
/** |
|
* Finds an element rendered by React with the supplied ID. |
|
* |
|
* @param {string} id ID of a DOM node in the React component. |
|
* @return {DOMElement} Root DOM node of the React component. |
|
*/ |
|
findReactNodeByID: function(id) { |
|
var reactRoot = ReactMount.findReactContainerForID(id); |
|
return ReactMount.findComponentRoot(reactRoot, id); |
|
}, |
|
|
|
/** |
|
* True if the supplied `node` is rendered by React. |
|
* |
|
* @param {*} node DOM Element to check. |
|
* @return {boolean} True if the DOM Element appears to be rendered by React. |
|
* @internal |
|
*/ |
|
isRenderedByReact: function(node) { |
|
if (node.nodeType !== 1) { |
|
// Not a DOMElement, therefore not a React component |
|
return false; |
|
} |
|
var id = ReactMount.getID(node); |
|
return id ? id.charAt(0) === SEPARATOR : false; |
|
}, |
|
|
|
/** |
|
* Traverses up the ancestors of the supplied node to find a node that is a |
|
* DOM representation of a React component. |
|
* |
|
* @param {*} node |
|
* @return {?DOMEventTarget} |
|
* @internal |
|
*/ |
|
getFirstReactDOM: function(node) { |
|
var current = node; |
|
while (current && current.parentNode !== current) { |
|
if (ReactMount.isRenderedByReact(current)) { |
|
return current; |
|
} |
|
current = current.parentNode; |
|
} |
|
return null; |
|
}, |
|
|
|
/** |
|
* Finds a node with the supplied `targetID` inside of the supplied |
|
* `ancestorNode`. Exploits the ID naming scheme to perform the search |
|
* quickly. |
|
* |
|
* @param {DOMEventTarget} ancestorNode Search from this root. |
|
* @pararm {string} targetID ID of the DOM representation of the component. |
|
* @return {DOMEventTarget} DOM node with the supplied `targetID`. |
|
* @internal |
|
*/ |
|
findComponentRoot: function(ancestorNode, targetID) { |
|
var firstChildren = findComponentRootReusableArray; |
|
var childIndex = 0; |
|
|
|
var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode; |
|
|
|
firstChildren[0] = deepestAncestor.firstChild; |
|
firstChildren.length = 1; |
|
|
|
while (childIndex < firstChildren.length) { |
|
var child = firstChildren[childIndex++]; |
|
var targetChild; |
|
|
|
while (child) { |
|
var childID = ReactMount.getID(child); |
|
if (childID) { |
|
// Even if we find the node we're looking for, we finish looping |
|
// through its siblings to ensure they're cached so that we don't have |
|
// to revisit this node again. Otherwise, we make n^2 calls to getID |
|
// when visiting the many children of a single node in order. |
|
|
|
if (targetID === childID) { |
|
targetChild = child; |
|
} else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) { |
|
// If we find a child whose ID is an ancestor of the given ID, |
|
// then we can be sure that we only want to search the subtree |
|
// rooted at this child, so we can throw out the rest of the |
|
// search state. |
|
firstChildren.length = childIndex = 0; |
|
firstChildren.push(child.firstChild); |
|
} |
|
|
|
} else { |
|
// If this child had no ID, then there's a chance that it was |
|
// injected automatically by the browser, as when a `<table>` |
|
// element sprouts an extra `<tbody>` child as a side effect of |
|
// `.innerHTML` parsing. Optimistically continue down this |
|
// branch, but not before examining the other siblings. |
|
firstChildren.push(child.firstChild); |
|
} |
|
|
|
child = child.nextSibling; |
|
} |
|
|
|
if (targetChild) { |
|
// Emptying firstChildren/findComponentRootReusableArray is |
|
// not necessary for correctness, but it helps the GC reclaim |
|
// any nodes that were left at the end of the search. |
|
firstChildren.length = 0; |
|
|
|
return targetChild; |
|
} |
|
} |
|
|
|
firstChildren.length = 0; |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
false, |
|
'findComponentRoot(..., %s): Unable to find element. This probably ' + |
|
'means the DOM was unexpectedly mutated (e.g., by the browser), ' + |
|
'usually due to forgetting a <tbody> when using tables, nesting tags ' + |
|
'like <form>, <p>, or <a>, or using non-SVG elements in an <svg> ' + |
|
'parent. ' + |
|
'Try inspecting the child nodes of the element with React ID `%s`.', |
|
targetID, |
|
ReactMount.getID(ancestorNode) |
|
) : invariant(false)); |
|
}, |
|
|
|
_mountImageIntoNode: function(markup, container, shouldReuseMarkup) { |
|
("production" !== process.env.NODE_ENV ? invariant( |
|
container && ( |
|
(container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) |
|
), |
|
'mountComponentIntoNode(...): Target container is not valid.' |
|
) : invariant(container && ( |
|
(container.nodeType === ELEMENT_NODE_TYPE || container.nodeType === DOC_NODE_TYPE) |
|
))); |
|
|
|
if (shouldReuseMarkup) { |
|
var rootElement = getReactRootElementInContainer(container); |
|
if (ReactMarkupChecksum.canReuseMarkup(markup, rootElement)) { |
|
return; |
|
} else { |
|
var checksum = rootElement.getAttribute( |
|
ReactMarkupChecksum.CHECKSUM_ATTR_NAME |
|
); |
|
rootElement.removeAttribute(ReactMarkupChecksum.CHECKSUM_ATTR_NAME); |
|
|
|
var rootMarkup = rootElement.outerHTML; |
|
rootElement.setAttribute( |
|
ReactMarkupChecksum.CHECKSUM_ATTR_NAME, |
|
checksum |
|
); |
|
|
|
var diffIndex = firstDifferenceIndex(markup, rootMarkup); |
|
var difference = ' (client) ' + |
|
markup.substring(diffIndex - 20, diffIndex + 20) + |
|
'\n (server) ' + rootMarkup.substring(diffIndex - 20, diffIndex + 20); |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
container.nodeType !== DOC_NODE_TYPE, |
|
'You\'re trying to render a component to the document using ' + |
|
'server rendering but the checksum was invalid. This usually ' + |
|
'means you rendered a different component type or props on ' + |
|
'the client from the one on the server, or your render() ' + |
|
'methods are impure. React cannot handle this case due to ' + |
|
'cross-browser 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%s', |
|
difference |
|
) : invariant(container.nodeType !== DOC_NODE_TYPE)); |
|
|
|
if ("production" !== process.env.NODE_ENV) { |
|
("production" !== process.env.NODE_ENV ? warning( |
|
false, |
|
'React attempted to reuse markup in a container but the ' + |
|
'checksum was invalid. This generally means that you are ' + |
|
'using server rendering and the markup generated on the ' + |
|
'server was not what the client was expecting. React injected ' + |
|
'new markup to compensate which works but you have lost many ' + |
|
'of the benefits of server rendering. Instead, figure out ' + |
|
'why the markup being generated is different on the client ' + |
|
'or server:\n%s', |
|
difference |
|
) : null); |
|
} |
|
} |
|
} |
|
|
|
("production" !== process.env.NODE_ENV ? invariant( |
|
container.nodeType !== DOC_NODE_TYPE, |
|
'You\'re trying to render a component to the document but ' + |
|
'you didn\'t use server rendering. We can\'t do this ' + |
|
'without using server rendering due to cross-browser quirks. ' + |
|
'See React.renderToString() for server rendering.' |
|
) : invariant(container.nodeType !== DOC_NODE_TYPE)); |
|
|
|
setInnerHTML(container, markup); |
|
}, |
|
|
|
/** |
|
* React ID utilities. |
|
*/ |
|
|
|
getReactRootID: getReactRootID, |
|
|
|
getID: getID, |
|
|
|
setID: setID, |
|
|
|
getNode: getNode, |
|
|
|
getNodeFromInstance: getNodeFromInstance, |
|
|
|
purgeID: purgeID |
|
}; |
|
|
|
ReactPerf.measureMethods(ReactMount, 'ReactMount', { |
|
_renderNewRootComponent: '_renderNewRootComponent', |
|
_mountImageIntoNode: '_mountImageIntoNode' |
|
}); |
|
|
|
module.exports = ReactMount;
|
|
|