Permalink
Cannot retrieve contributors at this time
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; |