/** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @providesModule ReactChildReconciler */ 'use strict'; var ReactReconciler = require('ReactReconciler'); var instantiateReactComponent = require('instantiateReactComponent'); var KeyEscapeUtils = require('KeyEscapeUtils'); var shouldUpdateReactComponent = require('shouldUpdateReactComponent'); var traverseAllChildren = require('traverseAllChildren'); var warning = require('warning'); var ReactComponentTreeHook; if ( typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'test' ) { // Temporary hack. // Inline requires don't work well with Jest: // https://github.com/facebook/react/issues/7240 // Remove the inline requires when we don't need them anymore: // https://github.com/facebook/react/pull/7178 ReactComponentTreeHook = require('ReactComponentTreeHook'); } function instantiateChild(childInstances, child, name, selfDebugID) { // We found a component instance. var keyUnique = childInstances[name] === undefined; if (__DEV__) { if (!ReactComponentTreeHook) { ReactComponentTreeHook = require('ReactComponentTreeHook'); } if (!keyUnique) { warning( false, 'flattenChildren(...): Encountered two children with the same key, ' + '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), ReactComponentTreeHook.getStackAddendumByID(selfDebugID), ); } } if (child != null && keyUnique) { childInstances[name] = instantiateReactComponent(child, true); } } /** * ReactChildReconciler provides helpers for initializing or updating a set of * children. Its output is suitable for passing it onto ReactMultiChild which * does diffed reordering and insertion. */ var ReactChildReconciler = { /** * Generates a "mount image" for each of the supplied children. In the case * of `ReactDOMComponent`, a mount image is a string of markup. * * @param {?object} nestedChildNodes Nested child maps. * @return {?object} A set of child instances. * @internal */ instantiateChildren: function( nestedChildNodes, transaction, context, selfDebugID, // 0 in production and for roots ) { if (nestedChildNodes == null) { return null; } var childInstances = {}; if (__DEV__) { traverseAllChildren( nestedChildNodes, (childInsts, child, name) => instantiateChild(childInsts, child, name, selfDebugID), childInstances, ); } else { traverseAllChildren(nestedChildNodes, instantiateChild, childInstances); } return childInstances; }, /** * Updates the rendered children and returns a new set of children. * * @param {?object} prevChildren Previously initialized set of children. * @param {?object} nextChildren Flat child element maps. * @param {ReactReconcileTransaction} transaction * @param {object} context * @return {?object} A new set of child instances. * @internal */ updateChildren: function( prevChildren, nextChildren, mountImages, removedNodes, transaction, hostParent, hostContainerInfo, context, selfDebugID, // 0 in production and for roots ) { // We currently don't have a way to track moves here but if we use iterators // instead of for..in we can zip the iterators and check if an item has // moved. // TODO: If nothing has changed, return the prevChildren object so that we // can quickly bailout if nothing has changed. if (!nextChildren && !prevChildren) { return; } var name; var prevChild; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } prevChild = prevChildren && prevChildren[name]; var prevElement = prevChild && prevChild._currentElement; var nextElement = nextChildren[name]; if ( prevChild != null && shouldUpdateReactComponent(prevElement, nextElement) ) { ReactReconciler.receiveComponent( prevChild, nextElement, transaction, context, ); nextChildren[name] = prevChild; } else { if (prevChild) { removedNodes[name] = ReactReconciler.getHostNode(prevChild); ReactReconciler.unmountComponent(prevChild, false); } // The child must be instantiated before it's mounted. var nextChildInstance = instantiateReactComponent(nextElement, true); nextChildren[name] = nextChildInstance; // Creating mount image now ensures refs are resolved in right order // (see https://github.com/facebook/react/pull/7101 for explanation). var nextChildMountImage = ReactReconciler.mountComponent( nextChildInstance, transaction, hostParent, hostContainerInfo, context, selfDebugID, ); mountImages.push(nextChildMountImage); } } // Unmount children that are no longer present. for (name in prevChildren) { if ( prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name)) ) { prevChild = prevChildren[name]; removedNodes[name] = ReactReconciler.getHostNode(prevChild); ReactReconciler.unmountComponent(prevChild, false); } } }, /** * Unmounts all rendered children. This should be used to clean up children * when this component is unmounted. * * @param {?object} renderedChildren Previously initialized set of children. * @internal */ unmountChildren: function(renderedChildren, safely) { for (var name in renderedChildren) { if (renderedChildren.hasOwnProperty(name)) { var renderedChild = renderedChildren[name]; ReactReconciler.unmountComponent(renderedChild, safely); } } }, }; module.exports = ReactChildReconciler;