UNPKG

d2-ui

Version:
496 lines (462 loc) 14.7 kB
/** * 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 ReactMultiChild * @typechecks static-only */ 'use strict'; var ReactComponentEnvironment = require('./ReactComponentEnvironment'); var ReactMultiChildUpdateTypes = require('./ReactMultiChildUpdateTypes'); var ReactCurrentOwner = require('./ReactCurrentOwner'); var ReactReconciler = require('./ReactReconciler'); var ReactChildReconciler = require('./ReactChildReconciler'); var flattenChildren = require('./flattenChildren'); /** * Updating children of a component may trigger recursive updates. The depth is * used to batch recursive updates to render markup more efficiently. * * @type {number} * @private */ var updateDepth = 0; /** * Queue of update configuration objects. * * Each object has a `type` property that is in `ReactMultiChildUpdateTypes`. * * @type {array<object>} * @private */ var updateQueue = []; /** * Queue of markup to be rendered. * * @type {array<string>} * @private */ var markupQueue = []; /** * Enqueues markup to be rendered and inserted at a supplied index. * * @param {string} parentID ID of the parent component. * @param {string} markup Markup that renders into an element. * @param {number} toIndex Destination index. * @private */ function enqueueInsertMarkup(parentID, markup, toIndex) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.INSERT_MARKUP, markupIndex: markupQueue.push(markup) - 1, content: null, fromIndex: null, toIndex: toIndex }); } /** * Enqueues moving an existing element to another index. * * @param {string} parentID ID of the parent component. * @param {number} fromIndex Source index of the existing element. * @param {number} toIndex Destination index of the element. * @private */ function enqueueMove(parentID, fromIndex, toIndex) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.MOVE_EXISTING, markupIndex: null, content: null, fromIndex: fromIndex, toIndex: toIndex }); } /** * Enqueues removing an element at an index. * * @param {string} parentID ID of the parent component. * @param {number} fromIndex Index of the element to remove. * @private */ function enqueueRemove(parentID, fromIndex) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.REMOVE_NODE, markupIndex: null, content: null, fromIndex: fromIndex, toIndex: null }); } /** * Enqueues setting the markup of a node. * * @param {string} parentID ID of the parent component. * @param {string} markup Markup that renders into an element. * @private */ function enqueueSetMarkup(parentID, markup) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.SET_MARKUP, markupIndex: null, content: markup, fromIndex: null, toIndex: null }); } /** * Enqueues setting the text content. * * @param {string} parentID ID of the parent component. * @param {string} textContent Text content to set. * @private */ function enqueueTextContent(parentID, textContent) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.TEXT_CONTENT, markupIndex: null, content: textContent, fromIndex: null, toIndex: null }); } /** * Processes any enqueued updates. * * @private */ function processQueue() { if (updateQueue.length) { ReactComponentEnvironment.processChildrenUpdates(updateQueue, markupQueue); clearQueue(); } } /** * Clears any enqueued updates. * * @private */ function clearQueue() { updateQueue.length = 0; markupQueue.length = 0; } /** * ReactMultiChild are capable of reconciling multiple children. * * @class ReactMultiChild * @internal */ var ReactMultiChild = { /** * Provides common functionality for components that must reconcile multiple * children. This is used by `ReactDOMComponent` to mount, update, and * unmount child components. * * @lends {ReactMultiChild.prototype} */ Mixin: { _reconcilerInstantiateChildren: function (nestedChildren, transaction, context) { if (process.env.NODE_ENV !== 'production') { if (this._currentElement) { try { ReactCurrentOwner.current = this._currentElement._owner; return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context); } finally { ReactCurrentOwner.current = null; } } } return ReactChildReconciler.instantiateChildren(nestedChildren, transaction, context); }, _reconcilerUpdateChildren: function (prevChildren, nextNestedChildrenElements, transaction, context) { var nextChildren; if (process.env.NODE_ENV !== 'production') { if (this._currentElement) { try { ReactCurrentOwner.current = this._currentElement._owner; nextChildren = flattenChildren(nextNestedChildrenElements); } finally { ReactCurrentOwner.current = null; } return ReactChildReconciler.updateChildren(prevChildren, nextChildren, transaction, context); } } nextChildren = flattenChildren(nextNestedChildrenElements); return ReactChildReconciler.updateChildren(prevChildren, nextChildren, transaction, context); }, /** * 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]; // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; var mountImage = ReactReconciler.mountComponent(child, rootID, transaction, context); child._mountIndex = index++; mountImages.push(mountImage); } } return mountImages; }, /** * Replaces any rendered children with a text content string. * * @param {string} nextContent String of content. * @internal */ updateTextContent: function (nextContent) { updateDepth++; var errorThrown = true; try { var prevChildren = this._renderedChildren; // Remove any rendered children. ReactChildReconciler.unmountChildren(prevChildren); // TODO: The setTextContent operation should be enough for (var name in prevChildren) { if (prevChildren.hasOwnProperty(name)) { this._unmountChild(prevChildren[name]); } } // Set new text content. this.setTextContent(nextContent); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); } } } }, /** * Replaces any rendered children with a markup string. * * @param {string} nextMarkup String of markup. * @internal */ updateMarkup: function (nextMarkup) { updateDepth++; var errorThrown = true; try { var prevChildren = this._renderedChildren; // Remove any rendered children. ReactChildReconciler.unmountChildren(prevChildren); for (var name in prevChildren) { if (prevChildren.hasOwnProperty(name)) { this._unmountChildByName(prevChildren[name], name); } } this.setMarkup(nextMarkup); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); } } } }, /** * Updates the rendered children with new children. * * @param {?object} nextNestedChildrenElements Nested child element maps. * @param {ReactReconcileTransaction} transaction * @internal */ updateChildren: function (nextNestedChildrenElements, transaction, context) { updateDepth++; var errorThrown = true; try { this._updateChildren(nextNestedChildrenElements, transaction, context); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { if (errorThrown) { clearQueue(); } else { processQueue(); } } } }, /** * Improve performance by isolating this hot code path from the try/catch * block in `updateChildren`. * * @param {?object} nextNestedChildrenElements Nested child element maps. * @param {ReactReconcileTransaction} transaction * @final * @protected */ _updateChildren: function (nextNestedChildrenElements, transaction, context) { var prevChildren = this._renderedChildren; var nextChildren = this._reconcilerUpdateChildren(prevChildren, nextNestedChildrenElements, transaction, context); this._renderedChildren = nextChildren; if (!nextChildren && !prevChildren) { return; } var name; // `nextIndex` will increment for each child in `nextChildren`, but // `lastIndex` will be the last index visited in `prevChildren`. var lastIndex = 0; var nextIndex = 0; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var nextChild = nextChildren[name]; if (prevChild === nextChild) { this.moveChild(prevChild, 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); this._unmountChild(prevChild); } // The child must be instantiated before it's mounted. this._mountChildByNameAtIndex(nextChild, name, nextIndex, transaction, context); } nextIndex++; } // Remove children that are no longer present. for (name in prevChildren) { if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren.hasOwnProperty(name))) { this._unmountChild(prevChildren[name]); } } }, /** * Unmounts all rendered children. This should be used to clean up children * when this component is unmounted. * * @internal */ unmountChildren: function () { var renderedChildren = this._renderedChildren; ReactChildReconciler.unmountChildren(renderedChildren); 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, 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) { enqueueMove(this._rootNodeID, child._mountIndex, toIndex); } }, /** * Creates a child component. * * @param {ReactComponent} child Component to create. * @param {string} mountImage Markup to insert. * @protected */ createChild: function (child, mountImage) { enqueueInsertMarkup(this._rootNodeID, mountImage, child._mountIndex); }, /** * Removes a child component. * * @param {ReactComponent} child Child to remove. * @protected */ removeChild: function (child) { enqueueRemove(this._rootNodeID, child._mountIndex); }, /** * Sets this text content string. * * @param {string} textContent Text content to set. * @protected */ setTextContent: function (textContent) { enqueueTextContent(this._rootNodeID, textContent); }, /** * Sets this markup string. * * @param {string} markup Markup to set. * @protected */ setMarkup: function (markup) { enqueueSetMarkup(this._rootNodeID, markup); }, /** * 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 */ _mountChildByNameAtIndex: function (child, name, index, transaction, context) { // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; var mountImage = ReactReconciler.mountComponent(child, rootID, transaction, context); child._mountIndex = index; this.createChild(child, 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) { this.removeChild(child); child._mountIndex = null; } } }; module.exports = ReactMultiChild;