Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| /** | |
| * Copyright 2013-present, 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 ReactMultiChild | |
| */ | |
| 'use strict'; | |
| var ReactComponentEnvironment = require('ReactComponentEnvironment'); | |
| var ReactInstanceMap = require('ReactInstanceMap'); | |
| var ReactInstrumentation = require('ReactInstrumentation'); | |
| var ReactCurrentOwner = require('ReactCurrentOwner'); | |
| var ReactReconciler = require('ReactReconciler'); | |
| var ReactChildReconciler = require('ReactChildReconciler'); | |
| var emptyFunction = require('emptyFunction'); | |
| var flattenChildren = require('flattenChildren'); | |
| var invariant = require('invariant'); | |
| /** | |
| * Make an update for markup to be rendered and inserted at a supplied index. | |
| * | |
| * @param {string} markup Markup that renders into an element. | |
| * @param {number} toIndex Destination index. | |
| * @private | |
| */ | |
| function makeInsertMarkup(markup, afterNode, toIndex) { | |
| // NOTE: Null values reduce hidden classes. | |
| return { | |
| type: 'INSERT_MARKUP', | |
| content: markup, | |
| fromIndex: null, | |
| fromNode: null, | |
| toIndex: toIndex, | |
| afterNode: afterNode, | |
| }; | |
| } | |
| /** | |
| * Make an update for moving an existing element to another index. | |
| * | |
| * @param {number} fromIndex Source index of the existing element. | |
| * @param {number} toIndex Destination index of the element. | |
| * @private | |
| */ | |
| function makeMove(child, afterNode, toIndex) { | |
| // NOTE: Null values reduce hidden classes. | |
| return { | |
| type: 'MOVE_EXISTING', | |
| content: null, | |
| fromIndex: child._mountIndex, | |
| fromNode: ReactReconciler.getHostNode(child), | |
| toIndex: toIndex, | |
| afterNode: afterNode, | |
| }; | |
| } | |
| /** | |
| * Make an update for removing an element at an index. | |
| * | |
| * @param {number} fromIndex Index of the element to remove. | |
| * @private | |
| */ | |
| function makeRemove(child, node) { | |
| // NOTE: Null values reduce hidden classes. | |
| return { | |
| type: 'REMOVE_NODE', | |
| content: null, | |
| fromIndex: child._mountIndex, | |
| fromNode: node, | |
| toIndex: null, | |
| afterNode: null, | |
| }; | |
| } | |
| /** | |
| * Make an update for setting the markup of a node. | |
| * | |
| * @param {string} markup Markup that renders into an element. | |
| * @private | |
| */ | |
| function makeSetMarkup(markup) { | |
| // NOTE: Null values reduce hidden classes. | |
| return { | |
| type: 'SET_MARKUP', | |
| content: markup, | |
| fromIndex: null, | |
| fromNode: null, | |
| toIndex: null, | |
| afterNode: null, | |
| }; | |
| } | |
| /** | |
| * Make an update for setting the text content. | |
| * | |
| * @param {string} textContent Text content to set. | |
| * @private | |
| */ | |
| function makeTextContent(textContent) { | |
| // NOTE: Null values reduce hidden classes. | |
| return { | |
| type: 'TEXT_CONTENT', | |
| content: textContent, | |
| fromIndex: null, | |
| fromNode: null, | |
| toIndex: null, | |
| afterNode: null, | |
| }; | |
| } | |
| /** | |
| * Push an update, if any, onto the queue. Creates a new queue if none is | |
| * passed and always returns the queue. Mutative. | |
| */ | |
| function enqueue(queue, update) { | |
| if (update) { | |
| queue = queue || []; | |
| queue.push(update); | |
| } | |
| return queue; | |
| } | |
| /** | |
| * Processes any enqueued updates. | |
| * | |
| * @private | |
| */ | |
| function processQueue(inst, updateQueue) { | |
| ReactComponentEnvironment.processChildrenUpdates( | |
| inst, | |
| updateQueue, | |
| ); | |
| } | |
| var setChildrenForInstrumentation = emptyFunction; | |
| if (__DEV__) { | |
| var getDebugID = function(inst) { | |
| if (!inst._debugID) { | |
| // Check for ART-like instances. TODO: This is silly/gross. | |
| var internal; | |
| if ((internal = ReactInstanceMap.get(inst))) { | |
| inst = internal; | |
| } | |
| } | |
| return inst._debugID; | |
| }; | |
| setChildrenForInstrumentation = function(children) { | |
| var debugID = getDebugID(this); | |
| // TODO: React Native empty components are also multichild. | |
| // This means they still get into this method but don't have _debugID. | |
| if (debugID !== 0) { | |
| ReactInstrumentation.debugTool.onSetChildren( | |
| debugID, | |
| children ? Object.keys(children).map(key => children[key]._debugID) : [] | |
| ); | |
| } | |
| }; | |
| } | |
| /** | |
| * Provides common functionality for components that must reconcile multiple | |
| * children. This is used by `ReactDOMComponent` to mount, update, and | |
| * unmount child components. | |
| */ | |
| var ReactMultiChild = { | |
| _reconcilerInstantiateChildren: function(nestedChildren, transaction, context) { | |
| if (__DEV__) { | |
| var selfDebugID = getDebugID(this); | |
| if (this._currentElement) { | |
| try { | |
| ReactCurrentOwner.current = this._currentElement._owner; | |
| return ReactChildReconciler.instantiateChildren( | |
| nestedChildren, transaction, context, selfDebugID | |
| ); | |
| } finally { | |
| ReactCurrentOwner.current = null; | |
| } | |
| } | |
| } | |
| return ReactChildReconciler.instantiateChildren( | |
| nestedChildren, transaction, context | |
| ); | |
| }, | |
| _reconcilerUpdateChildren: function( | |
| prevChildren, | |
| nextNestedChildrenElements, | |
| mountImages, | |
| removedNodes, | |
| transaction, | |
| context | |
| ) { | |
| var nextChildren; | |
| var selfDebugID = 0; | |
| if (__DEV__) { | |
| selfDebugID = getDebugID(this); | |
| if (this._currentElement) { | |
| try { | |
| ReactCurrentOwner.current = this._currentElement._owner; | |
| nextChildren = flattenChildren(nextNestedChildrenElements, selfDebugID); | |
| } finally { | |
| ReactCurrentOwner.current = null; | |
| } | |
| ReactChildReconciler.updateChildren( | |
| prevChildren, | |
| nextChildren, | |
| mountImages, | |
| removedNodes, | |
| transaction, | |
| this, | |
| this._hostContainerInfo, | |
| context, | |
| selfDebugID | |
| ); | |
| return nextChildren; | |
| } | |
| } | |
| nextChildren = flattenChildren(nextNestedChildrenElements, selfDebugID); | |
| ReactChildReconciler.updateChildren( | |
| prevChildren, | |
| nextChildren, | |
| mountImages, | |
| removedNodes, | |
| transaction, | |
| this, | |
| this._hostContainerInfo, | |
| context, | |
| selfDebugID | |
| ); | |
| return nextChildren; | |
| }, | |
| /** | |
| * 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} nestedChildren Nested child maps. | |
| * @return {array} An array of mounted representations. | |
| * @internal | |
| */ | |
| mountChildren: function(nestedChildren, transaction, context) { | |
| var children = this._reconcilerInstantiateChildren( | |
| nestedChildren, transaction, context | |
| ); | |
| this._renderedChildren = children; | |
| var mountImages = []; | |
| var index = 0; | |
| for (var name in children) { | |
| if (children.hasOwnProperty(name)) { | |
| var child = children[name]; | |
| var selfDebugID = 0; | |
| if (__DEV__) { | |
| selfDebugID = getDebugID(this); | |
| } | |
| var mountImage = ReactReconciler.mountComponent( | |
| child, | |
| transaction, | |
| this, | |
| this._hostContainerInfo, | |
| context, | |
| selfDebugID | |
| ); | |
| child._mountIndex = index++; | |
| mountImages.push(mountImage); | |
| } | |
| } | |
| if (__DEV__) { | |
| setChildrenForInstrumentation.call(this, children); | |
| } | |
| return mountImages; | |
| }, | |
| /** | |
| * Replaces any rendered children with a text content string. | |
| * | |
| * @param {string} nextContent String of content. | |
| * @internal | |
| */ | |
| updateTextContent: function(nextContent) { | |
| var prevChildren = this._renderedChildren; | |
| // Remove any rendered children. | |
| ReactChildReconciler.unmountChildren(prevChildren, false); | |
| for (var name in prevChildren) { | |
| if (prevChildren.hasOwnProperty(name)) { | |
| invariant(false, 'updateTextContent called on non-empty component.'); | |
| } | |
| } | |
| // Set new text content. | |
| var updates = [makeTextContent(nextContent)]; | |
| processQueue(this, updates); | |
| }, | |
| /** | |
| * Replaces any rendered children with a markup string. | |
| * | |
| * @param {string} nextMarkup String of markup. | |
| * @internal | |
| */ | |
| updateMarkup: function(nextMarkup) { | |
| var prevChildren = this._renderedChildren; | |
| // Remove any rendered children. | |
| ReactChildReconciler.unmountChildren(prevChildren, false); | |
| for (var name in prevChildren) { | |
| if (prevChildren.hasOwnProperty(name)) { | |
| invariant(false, 'updateTextContent called on non-empty component.'); | |
| } | |
| } | |
| var updates = [makeSetMarkup(nextMarkup)]; | |
| processQueue(this, updates); | |
| }, | |
| /** | |
| * Updates the rendered children with new children. | |
| * | |
| * @param {?object} nextNestedChildrenElements Nested child element maps. | |
| * @param {ReactReconcileTransaction} transaction | |
| * @internal | |
| */ | |
| updateChildren: function(nextNestedChildrenElements, transaction, context) { | |
| // Hook used by React ART | |
| this._updateChildren(nextNestedChildrenElements, transaction, context); | |
| }, | |
| /** | |
| * @param {?object} nextNestedChildrenElements Nested child element maps. | |
| * @param {ReactReconcileTransaction} transaction | |
| * @final | |
| * @protected | |
| */ | |
| _updateChildren: function(nextNestedChildrenElements, transaction, context) { | |
| var prevChildren = this._renderedChildren; | |
| var removedNodes = {}; | |
| var mountImages = []; | |
| var nextChildren = this._reconcilerUpdateChildren( | |
| prevChildren, | |
| nextNestedChildrenElements, | |
| mountImages, | |
| removedNodes, | |
| transaction, | |
| context | |
| ); | |
| if (!nextChildren && !prevChildren) { | |
| return; | |
| } | |
| var updates = null; | |
| var name; | |
| // `nextIndex` will increment for each child in `nextChildren`, but | |
| // `lastIndex` will be the last index visited in `prevChildren`. | |
| var nextIndex = 0; | |
| var lastIndex = 0; | |
| // `nextMountIndex` will increment for each newly mounted child. | |
| var nextMountIndex = 0; | |
| var lastPlacedNode = null; | |
| for (name in nextChildren) { | |
| if (!nextChildren.hasOwnProperty(name)) { | |
| continue; | |
| } | |
| var prevChild = prevChildren && prevChildren[name]; | |
| var nextChild = nextChildren[name]; | |
| if (prevChild === nextChild) { | |
| updates = enqueue( | |
| updates, | |
| this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex) | |
| ); | |
| lastIndex = Math.max(prevChild._mountIndex, lastIndex); | |
| prevChild._mountIndex = nextIndex; | |
| } else { | |
| if (prevChild) { | |
| // Update `lastIndex` before `_mountIndex` gets unset by unmounting. | |
| lastIndex = Math.max(prevChild._mountIndex, lastIndex); | |
| // The `removedNodes` loop below will actually remove the child. | |
| } | |
| // The child must be instantiated before it's mounted. | |
| updates = enqueue( | |
| updates, | |
| this._mountChildAtIndex( | |
| nextChild, | |
| mountImages[nextMountIndex], | |
| lastPlacedNode, | |
| nextIndex, | |
| transaction, | |
| context | |
| ) | |
| ); | |
| nextMountIndex++; | |
| } | |
| nextIndex++; | |
| lastPlacedNode = ReactReconciler.getHostNode(nextChild); | |
| } | |
| // Remove children that are no longer present. | |
| for (name in removedNodes) { | |
| if (removedNodes.hasOwnProperty(name)) { | |
| updates = enqueue( | |
| updates, | |
| this._unmountChild(prevChildren[name], removedNodes[name]) | |
| ); | |
| } | |
| } | |
| if (updates) { | |
| processQueue(this, updates); | |
| } | |
| this._renderedChildren = nextChildren; | |
| if (__DEV__) { | |
| setChildrenForInstrumentation.call(this, nextChildren); | |
| } | |
| }, | |
| /** | |
| * Unmounts all rendered children. This should be used to clean up children | |
| * when this component is unmounted. It does not actually perform any | |
| * backend operations. | |
| * | |
| * @internal | |
| */ | |
| unmountChildren: function(safely) { | |
| var renderedChildren = this._renderedChildren; | |
| ReactChildReconciler.unmountChildren(renderedChildren, safely); | |
| this._renderedChildren = null; | |
| }, | |
| /** | |
| * Moves a child component to the supplied index. | |
| * | |
| * @param {ReactComponent} child Component to move. | |
| * @param {number} toIndex Destination index of the element. | |
| * @param {number} lastIndex Last index visited of the siblings of `child`. | |
| * @protected | |
| */ | |
| moveChild: function(child, afterNode, toIndex, lastIndex) { | |
| // If the index of `child` is less than `lastIndex`, then it needs to | |
| // be moved. Otherwise, we do not need to move it because a child will be | |
| // inserted or moved before `child`. | |
| if (child._mountIndex < lastIndex) { | |
| return makeMove(child, afterNode, toIndex); | |
| } | |
| }, | |
| /** | |
| * Creates a child component. | |
| * | |
| * @param {ReactComponent} child Component to create. | |
| * @param {string} mountImage Markup to insert. | |
| * @protected | |
| */ | |
| createChild: function(child, afterNode, mountImage) { | |
| return makeInsertMarkup(mountImage, afterNode, child._mountIndex); | |
| }, | |
| /** | |
| * Removes a child component. | |
| * | |
| * @param {ReactComponent} child Child to remove. | |
| * @protected | |
| */ | |
| removeChild: function(child, node) { | |
| return makeRemove(child, node); | |
| }, | |
| /** | |
| * Mounts a child with the supplied name. | |
| * | |
| * NOTE: This is part of `updateChildren` and is here for readability. | |
| * | |
| * @param {ReactComponent} child Component to mount. | |
| * @param {string} name Name of the child. | |
| * @param {number} index Index at which to insert the child. | |
| * @param {ReactReconcileTransaction} transaction | |
| * @private | |
| */ | |
| _mountChildAtIndex: function( | |
| child, | |
| mountImage, | |
| afterNode, | |
| index, | |
| transaction, | |
| context) { | |
| child._mountIndex = index; | |
| return this.createChild(child, afterNode, mountImage); | |
| }, | |
| /** | |
| * Unmounts a rendered child. | |
| * | |
| * NOTE: This is part of `updateChildren` and is here for readability. | |
| * | |
| * @param {ReactComponent} child Component to unmount. | |
| * @private | |
| */ | |
| _unmountChild: function(child, node) { | |
| var update = this.removeChild(child, node); | |
| child._mountIndex = null; | |
| return update; | |
| }, | |
| }; | |
| module.exports = ReactMultiChild; |