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.
888 lines
30 KiB
888 lines
30 KiB
10 years ago
|
/**
|
||
|
* 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;
|