UNPKG

react-dom

Version:

React package for working with the DOM.

1,030 lines (922 loc) • 37.2 kB
/** * 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. * * */ 'use strict'; var _prodInvariant = require('./reactProdInvariant'); var REACT_ELEMENT_TYPE = require('./ReactElementSymbol'); var _require = require('./ReactCoroutine'), REACT_COROUTINE_TYPE = _require.REACT_COROUTINE_TYPE, REACT_YIELD_TYPE = _require.REACT_YIELD_TYPE; var _require2 = require('./ReactPortal'), REACT_PORTAL_TYPE = _require2.REACT_PORTAL_TYPE; var ReactFiber = require('./ReactFiber'); var ReactReifiedYield = require('./ReactReifiedYield'); var ReactTypeOfSideEffect = require('./ReactTypeOfSideEffect'); var ReactTypeOfWork = require('./ReactTypeOfWork'); var emptyObject = require('fbjs/lib/emptyObject'); var getIteratorFn = require('./getIteratorFn'); var invariant = require('fbjs/lib/invariant'); if (process.env.NODE_ENV !== 'production') { var _require3 = require('./ReactDebugCurrentFiber'), getCurrentFiberStackAddendum = _require3.getCurrentFiberStackAddendum; var warning = require('fbjs/lib/warning'); } var cloneFiber = ReactFiber.cloneFiber, createFiberFromElement = ReactFiber.createFiberFromElement, createFiberFromFragment = ReactFiber.createFiberFromFragment, createFiberFromText = ReactFiber.createFiberFromText, createFiberFromCoroutine = ReactFiber.createFiberFromCoroutine, createFiberFromYield = ReactFiber.createFiberFromYield, createFiberFromPortal = ReactFiber.createFiberFromPortal; var createReifiedYield = ReactReifiedYield.createReifiedYield, createUpdatedReifiedYield = ReactReifiedYield.createUpdatedReifiedYield; var isArray = Array.isArray; var ClassComponent = ReactTypeOfWork.ClassComponent, HostText = ReactTypeOfWork.HostText, HostPortal = ReactTypeOfWork.HostPortal, CoroutineComponent = ReactTypeOfWork.CoroutineComponent, YieldComponent = ReactTypeOfWork.YieldComponent, Fragment = ReactTypeOfWork.Fragment; var NoEffect = ReactTypeOfSideEffect.NoEffect, Placement = ReactTypeOfSideEffect.Placement, Deletion = ReactTypeOfSideEffect.Deletion; function coerceRef(current, element) { var mixedRef = element.ref; if (mixedRef != null && typeof mixedRef !== 'function') { if (element._owner) { var _ret = function () { var owner = element._owner; var inst = void 0; if (owner) { if (typeof owner.tag === 'number') { var ownerFiber = owner; !(ownerFiber.tag === ClassComponent) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Stateless function components cannot have refs.') : _prodInvariant('110') : void 0; inst = ownerFiber.stateNode; } else { // Stack inst = owner.getPublicInstance(); } } invariant(inst, 'Missing owner for string ref %s', mixedRef); var stringRef = String(mixedRef); // Check if previous string ref matches new string ref if (current && current.ref && current.ref._stringRef === stringRef) { return { v: current.ref }; } var ref = function (value) { var refs = inst.refs === emptyObject ? inst.refs = {} : inst.refs; if (value === null) { delete refs[stringRef]; } else { refs[stringRef] = value; } }; ref._stringRef = stringRef; return { v: ref }; }(); if (typeof _ret === "object") return _ret.v; } } return mixedRef; } // This wrapper function exists because I expect to clone the code in each path // to be able to optimize each path individually by branching early. This needs // a compiler or we can do it manually. Helpers that don't need this branching // live outside of this function. function ChildReconciler(shouldClone, shouldTrackSideEffects) { function deleteChild(returnFiber, childToDelete) { if (!shouldTrackSideEffects) { // Noop. return; } if (!shouldClone) { // When we're reconciling in place we have a work in progress copy. We // actually want the current copy. If there is no current copy, then we // don't need to track deletion side-effects. if (!childToDelete.alternate) { return; } childToDelete = childToDelete.alternate; } // Deletions are added in reversed order so we add it to the front. var last = returnFiber.progressedLastDeletion; if (last) { last.nextEffect = childToDelete; returnFiber.progressedLastDeletion = childToDelete; } else { returnFiber.progressedFirstDeletion = returnFiber.progressedLastDeletion = childToDelete; } childToDelete.nextEffect = null; childToDelete.effectTag = Deletion; } function deleteRemainingChildren(returnFiber, currentFirstChild) { if (!shouldTrackSideEffects) { // Noop. return null; } // TODO: For the shouldClone case, this could be micro-optimized a bit by // assuming that after the first child we've already added everything. var childToDelete = currentFirstChild; while (childToDelete) { deleteChild(returnFiber, childToDelete); childToDelete = childToDelete.sibling; } return null; } function mapRemainingChildren(returnFiber, currentFirstChild) { // Add the remaining children to a temporary map so that we can find them by // keys quickly. Implicit (null) keys get added to this set with their index var existingChildren = new Map(); var existingChild = currentFirstChild; while (existingChild) { if (existingChild.key !== null) { existingChildren.set(existingChild.key, existingChild); } else { existingChildren.set(existingChild.index, existingChild); } existingChild = existingChild.sibling; } return existingChildren; } function useFiber(fiber, priority) { // We currently set sibling to null and index to 0 here because it is easy // to forget to do before returning it. E.g. for the single child case. if (shouldClone) { var clone = cloneFiber(fiber, priority); clone.index = 0; clone.sibling = null; return clone; } else { // We override the pending priority even if it is higher, because if // we're reconciling at a lower priority that means that this was // down-prioritized. fiber.pendingWorkPriority = priority; fiber.effectTag = NoEffect; fiber.index = 0; fiber.sibling = null; return fiber; } } function placeChild(newFiber, lastPlacedIndex, newIndex) { newFiber.index = newIndex; if (!shouldTrackSideEffects) { // Noop. return lastPlacedIndex; } var current = newFiber.alternate; if (current) { var oldIndex = current.index; if (oldIndex < lastPlacedIndex) { // This is a move. newFiber.effectTag = Placement; return lastPlacedIndex; } else { // This item can stay in place. return oldIndex; } } else { // This is an insertion. newFiber.effectTag = Placement; return lastPlacedIndex; } } function placeSingleChild(newFiber) { // This is simpler for the single child case. We only need to do a // placement for inserting new children. if (shouldTrackSideEffects && !newFiber.alternate) { newFiber.effectTag = Placement; } return newFiber; } function updateTextNode(returnFiber, current, textContent, priority) { if (current == null || current.tag !== HostText) { // Insert var created = createFiberFromText(textContent, priority); created['return'] = returnFiber; return created; } else { // Update var existing = useFiber(current, priority); existing.pendingProps = textContent; existing['return'] = returnFiber; return existing; } } function updateElement(returnFiber, current, element, priority) { if (current == null || current.type !== element.type) { // Insert var created = createFiberFromElement(element, priority); created.ref = coerceRef(current, element); created['return'] = returnFiber; return created; } else { // Move based on index var existing = useFiber(current, priority); existing.ref = coerceRef(current, element); existing.pendingProps = element.props; existing['return'] = returnFiber; if (process.env.NODE_ENV !== 'production') { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } } function updateCoroutine(returnFiber, current, coroutine, priority) { // TODO: Should this also compare handler to determine whether to reuse? if (current == null || current.tag !== CoroutineComponent) { // Insert var created = createFiberFromCoroutine(coroutine, priority); created['return'] = returnFiber; return created; } else { // Move based on index var existing = useFiber(current, priority); existing.pendingProps = coroutine; existing['return'] = returnFiber; return existing; } } function updateYield(returnFiber, current, yieldNode, priority) { // TODO: Should this also compare continuation to determine whether to reuse? if (current == null || current.tag !== YieldComponent) { // Insert var reifiedYield = createReifiedYield(yieldNode); var created = createFiberFromYield(yieldNode, priority); created.type = reifiedYield; created['return'] = returnFiber; return created; } else { // Move based on index var existing = useFiber(current, priority); existing.type = createUpdatedReifiedYield(current.type, yieldNode); existing['return'] = returnFiber; return existing; } } function updatePortal(returnFiber, current, portal, priority) { if (current == null || current.tag !== HostPortal || current.stateNode.containerInfo !== portal.containerInfo || current.stateNode.implementation !== portal.implementation) { // Insert var created = createFiberFromPortal(portal, priority); created['return'] = returnFiber; return created; } else { // Update var existing = useFiber(current, priority); existing.pendingProps = portal.children || []; existing['return'] = returnFiber; return existing; } } function updateFragment(returnFiber, current, fragment, priority) { if (current == null || current.tag !== Fragment) { // Insert var created = createFiberFromFragment(fragment, priority); created['return'] = returnFiber; return created; } else { // Update var existing = useFiber(current, priority); existing.pendingProps = fragment; existing['return'] = returnFiber; return existing; } } function createChild(returnFiber, newChild, priority) { if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes doesn't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text // node. var created = createFiberFromText('' + newChild, priority); created['return'] = returnFiber; return created; } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { var _created = createFiberFromElement(newChild, priority); _created.ref = coerceRef(null, newChild); _created['return'] = returnFiber; return _created; } case REACT_COROUTINE_TYPE: { var _created2 = createFiberFromCoroutine(newChild, priority); _created2['return'] = returnFiber; return _created2; } case REACT_YIELD_TYPE: { var reifiedYield = createReifiedYield(newChild); var _created3 = createFiberFromYield(newChild, priority); _created3.type = reifiedYield; _created3['return'] = returnFiber; return _created3; } case REACT_PORTAL_TYPE: { var _created4 = createFiberFromPortal(newChild, priority); _created4['return'] = returnFiber; return _created4; } } if (isArray(newChild) || getIteratorFn(newChild)) { var _created5 = createFiberFromFragment(newChild, priority); _created5['return'] = returnFiber; return _created5; } } return null; } function updateSlot(returnFiber, oldFiber, newChild, priority) { // Update the fiber if the keys match, otherwise return null. var key = oldFiber ? oldFiber.key : null; if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes doesn't have keys. If the previous node is implicitly keyed // we can continue to replace it without aborting even if it is not a text // node. if (key !== null) { return null; } return updateTextNode(returnFiber, oldFiber, '' + newChild, priority); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { if (newChild.key === key) { return updateElement(returnFiber, oldFiber, newChild, priority); } else { return null; } } case REACT_COROUTINE_TYPE: { if (newChild.key === key) { return updateCoroutine(returnFiber, oldFiber, newChild, priority); } else { return null; } } case REACT_YIELD_TYPE: { if (newChild.key === key) { return updateYield(returnFiber, oldFiber, newChild, priority); } else { return null; } } } if (isArray(newChild) || getIteratorFn(newChild)) { // Fragments doesn't have keys so if the previous key is implicit we can // update it. if (key !== null) { return null; } return updateFragment(returnFiber, oldFiber, newChild, priority); } } return null; } function updateFromMap(existingChildren, returnFiber, newIdx, newChild, priority) { if (typeof newChild === 'string' || typeof newChild === 'number') { // Text nodes doesn't have keys, so we neither have to check the old nor // new node for the key. If both are text nodes, they match. var matchedFiber = existingChildren.get(newIdx) || null; return updateTextNode(returnFiber, matchedFiber, '' + newChild, priority); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: { var _matchedFiber = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null; return updateElement(returnFiber, _matchedFiber, newChild, priority); } case REACT_COROUTINE_TYPE: { var _matchedFiber2 = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null; return updateCoroutine(returnFiber, _matchedFiber2, newChild, priority); } case REACT_YIELD_TYPE: { var _matchedFiber3 = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null; return updateYield(returnFiber, _matchedFiber3, newChild, priority); } case REACT_PORTAL_TYPE: { var _matchedFiber4 = existingChildren.get(newChild.key === null ? newIdx : newChild.key) || null; return updatePortal(returnFiber, _matchedFiber4, newChild, priority); } } if (isArray(newChild) || getIteratorFn(newChild)) { var _matchedFiber5 = existingChildren.get(newIdx) || null; return updateFragment(returnFiber, _matchedFiber5, newChild, priority); } } return null; } function warnOnDuplicateKey(child, knownKeys) { if (process.env.NODE_ENV !== 'production') { if (typeof child !== 'object' || child == null) { return knownKeys; } switch (child.$$typeof) { case REACT_ELEMENT_TYPE: case REACT_COROUTINE_TYPE: case REACT_YIELD_TYPE: case REACT_PORTAL_TYPE: var key = child.key; if (typeof key !== 'string') { break; } if (knownKeys == null) { knownKeys = new Set(); knownKeys.add(key); break; } if (!knownKeys.has(key)) { knownKeys.add(key); break; } process.env.NODE_ENV !== 'production' ? warning(false, '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', key, getCurrentFiberStackAddendum()) : void 0; break; default: break; } } return knownKeys; } function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, priority) { // This algorithm can't optimize by searching from boths ends since we // don't have backpointers on fibers. I'm trying to see how far we can get // with that model. If it ends up not being worth the tradeoffs, we can // add it later. // Even with a two ended optimization, we'd want to optimize for the case // where there are few changes and brute force the comparison instead of // going for the Map. It'd like to explore hitting that path first in // forward-only mode and only go for the Map once we notice that we need // lots of look ahead. This doesn't handle reversal as well as two ended // search but that's unusual. Besides, for the two ended optimization to // work on Iterables, we'd need to copy the whole set. // In this first iteration, we'll just live with hitting the bad case // (adding everything to a Map) in for every insert/move. // If you change this code, also update reconcileChildrenIterator() which // uses the same algorithm. if (process.env.NODE_ENV !== 'production') { // First, validate keys. var knownKeys = null; for (var i = 0; i < newChildren.length; i++) { var child = newChildren[i]; knownKeys = warnOnDuplicateKey(child, knownKeys); } } var resultingFirstChild = null; var previousNewFiber = null; var oldFiber = currentFirstChild; var lastPlacedIndex = 0; var newIdx = 0; var nextOldFiber = null; for (; oldFiber && newIdx < newChildren.length; newIdx++) { if (oldFiber) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } } var newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], priority); if (!newFiber) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need // a better way to communicate whether this was a miss or null, // boolean, undefined, etc. if (!oldFiber) { oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) { if (oldFiber && !newFiber.alternate) { // We matched the slot, but we didn't reuse the existing fiber, so we // need to delete the existing child. deleteChild(returnFiber, oldFiber); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (!previousNewFiber) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { // TODO: Defer siblings if we're not at the right index for this slot. // I.e. if we had null values before, then we want to defer this // for each null value. However, we also don't want to call updateSlot // with the previous one. previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } if (!oldFiber) { // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; newIdx < newChildren.length; newIdx++) { var _newFiber = createChild(returnFiber, newChildren[newIdx], priority); if (!_newFiber) { continue; } lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx); if (!previousNewFiber) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = _newFiber; } else { previousNewFiber.sibling = _newFiber; } previousNewFiber = _newFiber; } return resultingFirstChild; } // Add all children to a key map for quick lookups. var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. for (; newIdx < newChildren.length; newIdx++) { var _newFiber2 = updateFromMap(existingChildren, returnFiber, newIdx, newChildren[newIdx], priority); if (_newFiber2) { if (shouldTrackSideEffects) { if (_newFiber2.alternate) { // The new fiber is a work in progress, but if there exists a // current, that means that we reused the fiber. We need to delete // it from the child list so that we don't add it to the deletion // list. existingChildren['delete'](_newFiber2.key === null ? newIdx : _newFiber2.key); } } lastPlacedIndex = placeChild(_newFiber2, lastPlacedIndex, newIdx); if (!previousNewFiber) { resultingFirstChild = _newFiber2; } else { previousNewFiber.sibling = _newFiber2; } previousNewFiber = _newFiber2; } } if (shouldTrackSideEffects) { // Any existing children that weren't consumed above were deleted. We need // to add them to the deletion list. existingChildren.forEach(function (child) { return deleteChild(returnFiber, child); }); } return resultingFirstChild; } function reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, priority) { // This is the same implementation as reconcileChildrenArray(), // but using the iterator instead. var iteratorFn = getIteratorFn(newChildrenIterable); if (typeof iteratorFn !== 'function') { throw new Error('An object is not an iterable.'); } if (process.env.NODE_ENV !== 'production') { // First, validate keys. // We'll get a different iterator later for the main pass. var _newChildren = iteratorFn.call(newChildrenIterable); if (_newChildren == null) { throw new Error('An iterable object provided no iterator.'); } var knownKeys = null; var _step = _newChildren.next(); for (; !_step.done; _step = _newChildren.next()) { var child = _step.value; knownKeys = warnOnDuplicateKey(child, knownKeys); } } var newChildren = iteratorFn.call(newChildrenIterable); if (newChildren == null) { throw new Error('An iterable object provided no iterator.'); } var resultingFirstChild = null; var previousNewFiber = null; var oldFiber = currentFirstChild; var lastPlacedIndex = 0; var newIdx = 0; var nextOldFiber = null; var step = newChildren.next(); for (; oldFiber && !step.done; newIdx++, step = newChildren.next()) { if (oldFiber) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } } var newFiber = updateSlot(returnFiber, oldFiber, step.value, priority); if (!newFiber) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need // a better way to communicate whether this was a miss or null, // boolean, undefined, etc. if (!oldFiber) { oldFiber = nextOldFiber; } break; } if (shouldTrackSideEffects) { if (oldFiber && !newFiber.alternate) { // We matched the slot, but we didn't reuse the existing fiber, so we // need to delete the existing child. deleteChild(returnFiber, oldFiber); } } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (!previousNewFiber) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { // TODO: Defer siblings if we're not at the right index for this slot. // I.e. if we had null values before, then we want to defer this // for each null value. However, we also don't want to call updateSlot // with the previous one. previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } if (step.done) { // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); return resultingFirstChild; } if (!oldFiber) { // If we don't have any more existing children we can choose a fast path // since the rest will all be insertions. for (; !step.done; newIdx++, step = newChildren.next()) { var _newFiber3 = createChild(returnFiber, step.value, priority); if (!_newFiber3) { continue; } lastPlacedIndex = placeChild(_newFiber3, lastPlacedIndex, newIdx); if (!previousNewFiber) { // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = _newFiber3; } else { previousNewFiber.sibling = _newFiber3; } previousNewFiber = _newFiber3; } return resultingFirstChild; } // Add all children to a key map for quick lookups. var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. for (; !step.done; newIdx++, step = newChildren.next()) { var _newFiber4 = updateFromMap(existingChildren, returnFiber, newIdx, step.value, priority); if (_newFiber4) { if (shouldTrackSideEffects) { if (_newFiber4.alternate) { // The new fiber is a work in progress, but if there exists a // current, that means that we reused the fiber. We need to delete // it from the child list so that we don't add it to the deletion // list. existingChildren['delete'](_newFiber4.key === null ? newIdx : _newFiber4.key); } } lastPlacedIndex = placeChild(_newFiber4, lastPlacedIndex, newIdx); if (!previousNewFiber) { resultingFirstChild = _newFiber4; } else { previousNewFiber.sibling = _newFiber4; } previousNewFiber = _newFiber4; } } if (shouldTrackSideEffects) { // Any existing children that weren't consumed above were deleted. We need // to add them to the deletion list. existingChildren.forEach(function (child) { return deleteChild(returnFiber, child); }); } return resultingFirstChild; } function reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, priority) { // There's no need to check for keys on text nodes since we don't have a // way to define them. if (currentFirstChild && currentFirstChild.tag === HostText) { // We already have an existing node so let's just update it and delete // the rest. deleteRemainingChildren(returnFiber, currentFirstChild.sibling); var existing = useFiber(currentFirstChild, priority); existing.pendingProps = textContent; existing['return'] = returnFiber; return existing; } // The existing first child is not a text node so we need to create one // and delete the existing ones. deleteRemainingChildren(returnFiber, currentFirstChild); var created = createFiberFromText(textContent, priority); created['return'] = returnFiber; return created; } function reconcileSingleElement(returnFiber, currentFirstChild, element, priority) { var key = element.key; var child = currentFirstChild; while (child) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { if (child.type === element.type) { deleteRemainingChildren(returnFiber, child.sibling); var existing = useFiber(child, priority); existing.ref = coerceRef(child, element); existing.pendingProps = element.props; existing['return'] = returnFiber; if (process.env.NODE_ENV !== 'production') { existing._debugSource = element._source; existing._debugOwner = element._owner; } return existing; } else { deleteRemainingChildren(returnFiber, child); break; } } else { deleteChild(returnFiber, child); } child = child.sibling; } var created = createFiberFromElement(element, priority); created.ref = coerceRef(currentFirstChild, element); created['return'] = returnFiber; return created; } function reconcileSingleCoroutine(returnFiber, currentFirstChild, coroutine, priority) { var key = coroutine.key; var child = currentFirstChild; while (child) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { if (child.tag === CoroutineComponent) { deleteRemainingChildren(returnFiber, child.sibling); var existing = useFiber(child, priority); existing.pendingProps = coroutine; existing['return'] = returnFiber; return existing; } else { deleteRemainingChildren(returnFiber, child); break; } } else { deleteChild(returnFiber, child); } child = child.sibling; } var created = createFiberFromCoroutine(coroutine, priority); created['return'] = returnFiber; return created; } function reconcileSingleYield(returnFiber, currentFirstChild, yieldNode, priority) { var key = yieldNode.key; var child = currentFirstChild; while (child) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { if (child.tag === YieldComponent) { deleteRemainingChildren(returnFiber, child.sibling); var existing = useFiber(child, priority); existing.type = createUpdatedReifiedYield(child.type, yieldNode); existing['return'] = returnFiber; return existing; } else { deleteRemainingChildren(returnFiber, child); break; } } else { deleteChild(returnFiber, child); } child = child.sibling; } var reifiedYield = createReifiedYield(yieldNode); var created = createFiberFromYield(yieldNode, priority); created.type = reifiedYield; created['return'] = returnFiber; return created; } function reconcileSinglePortal(returnFiber, currentFirstChild, portal, priority) { var key = portal.key; var child = currentFirstChild; while (child) { // TODO: If key === null and child.key === null, then this only applies to // the first item in the list. if (child.key === key) { if (child.tag === HostPortal && child.stateNode.containerInfo === portal.containerInfo && child.stateNode.implementation === portal.implementation) { deleteRemainingChildren(returnFiber, child.sibling); var existing = useFiber(child, priority); existing.pendingProps = portal.children || []; existing['return'] = returnFiber; return existing; } else { deleteRemainingChildren(returnFiber, child); break; } } else { deleteChild(returnFiber, child); } child = child.sibling; } var created = createFiberFromPortal(portal, priority); created['return'] = returnFiber; return created; } // This API will tag the children with the side-effect of the reconciliation // itself. They will be added to the side-effect list as we pass through the // children and the parent. function reconcileChildFibers(returnFiber, currentFirstChild, newChild, priority) { // This function is not recursive. // If the top level item is an array, we treat it as a set of children, // not as a fragment. Nested arrays on the other hand will be treated as // fragment nodes. Recursion happens at the normal flow. if (typeof newChild === 'string' || typeof newChild === 'number') { return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, priority)); } if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, priority)); case REACT_COROUTINE_TYPE: return placeSingleChild(reconcileSingleCoroutine(returnFiber, currentFirstChild, newChild, priority)); case REACT_YIELD_TYPE: return placeSingleChild(reconcileSingleYield(returnFiber, currentFirstChild, newChild, priority)); case REACT_PORTAL_TYPE: return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, priority)); } if (isArray(newChild)) { return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, priority); } if (getIteratorFn(newChild)) { return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, priority); } } // Remaining cases are all treated as empty. return deleteRemainingChildren(returnFiber, currentFirstChild); } return reconcileChildFibers; } exports.reconcileChildFibers = ChildReconciler(true, true); exports.reconcileChildFibersInPlace = ChildReconciler(false, true); exports.mountChildFibersInPlace = ChildReconciler(false, false); exports.cloneChildFibers = function (current, workInProgress) { if (!workInProgress.child) { return; } if (current && workInProgress.child === current.child) { // We use workInProgress.child since that lets Flow know that it can't be // null since we validated that already. However, as the line above suggests // they're actually the same thing. var currentChild = workInProgress.child; // TODO: This used to reset the pending priority. Not sure if that is needed. // workInProgress.pendingWorkPriority = current.pendingWorkPriority; // TODO: The below priority used to be set to NoWork which would've // dropped work. This is currently unobservable but will become // observable when the first sibling has lower priority work remaining // than the next sibling. At that point we should add tests that catches // this. var newChild = cloneFiber(currentChild, currentChild.pendingWorkPriority); workInProgress.child = newChild; newChild['return'] = workInProgress; while (currentChild.sibling) { currentChild = currentChild.sibling; newChild = newChild.sibling = cloneFiber(currentChild, currentChild.pendingWorkPriority); newChild['return'] = workInProgress; } newChild.sibling = null; } // If there is no alternate, then we don't need to clone the children. // If the children of the alternate fiber is a different set, then we don't // need to clone. We need to reset the return fiber though since we'll // traverse down into them. var child = workInProgress.child; while (child) { child['return'] = workInProgress; child = child.sibling; } };