react-dom 源码(5)beginWork

1,113 阅读10分钟

本文基于 v16.12.0 版本

beginWork$1

function beginWork$1(current?1, workInProgress, renderExpirationTime) {
  var updateExpirationTime = workInProgress.expirationTime;

  //...

  if (current?1 !== null) {
    var oldProps = current?1.memoizedProps;
    var newProps = workInProgress.pendingProps;

    if (oldProps !== newProps || hasContextChanged() || ( 
    // Force a re-render if the implementation changed due to hot reload:
    // 如果由于热重载而导致实现改变,则强制重新渲染: 
    workInProgress.type !== current?1.type)) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      didReceiveUpdate = true;
    } else if (updateExpirationTime < renderExpirationTime) {
      didReceiveUpdate = false; 
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          resetHydrationState();
          break;

        case HostComponent:
          pushHostContext(workInProgress);

          if (workInProgress.mode & ConcurrentMode && renderExpirationTime !== Never && shouldDeprioritizeSubtree(workInProgress.type, newProps)) {
            if (enableSchedulerTracing) {
              markSpawnedWork(Never);
            } 
            // Schedule this fiber to re-render at offscreen priority. Then bailout.
            workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
            return null;
          }

          break;

        case ClassComponent:
          {
            var Component = workInProgress.type;

            if (isContextProvider(Component)) {
              pushContextProvider(workInProgress);
            }

            break;
          }

        case HostPortal:
          pushHostContainer(workInProgress, workInProgress.stateNode.containerInfo);
          break;

        case ContextProvider:
          {
            var newValue = workInProgress.memoizedProps.value;
            pushProvider(workInProgress, newValue);
            break;
          }

        case Profiler:
          if (enableProfilerTimer) {
            // Profiler should only call onRender when one of its descendants actually rendered.
            var hasChildWork = workInProgress.childExpirationTime >= renderExpirationTime;

            if (hasChildWork) {
              workInProgress.effectTag |= Update;
            }
          }

          break;

        case SuspenseComponent:
          {
            var state = workInProgress.memoizedState;

            if (state !== null) {
              if (enableSuspenseServerRenderer) {
                if (state.dehydrated !== null) {
                  pushSuspenseContext(workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current)); 
                  // We know that this component will suspend again because if it has
                  // been unsuspended it has committed as a resolved Suspense component.
                  // If it needs to be retried, it should have work scheduled on it.

                  workInProgress.effectTag |= DidCapture;
                  break;
                }
              } 
              // If this boundary is currently timed out, we need to decide
              // whether to retry the primary children, or to skip over it and
              // go straight to the fallback. Check the priority of the primary
              // child fragment.


              var primaryChildFragment = workInProgress.child;
              var primaryChildExpirationTime = primaryChildFragment.childExpirationTime;

              if (primaryChildExpirationTime !== NoWork && primaryChildExpirationTime >= renderExpirationTime) {
                // The primary children have pending work. Use the normal path
                // to attempt to render the primary children again.
                return updateSuspenseComponent(current?1, workInProgress, renderExpirationTime);
              } else {
                pushSuspenseContext(workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current)); 
                // The primary children do not have pending work with sufficient
                // priority. Bailout.

                var child = bailoutOnAlreadyFinishedWork(current?1, workInProgress, renderExpirationTime);

                if (child !== null) {
                  // The fallback children have pending work. Skip over the
                  // primary children and work on the fallback.
                  return child.sibling;
                } else {
                  return null;
                }
              }
            } else {
              pushSuspenseContext(workInProgress, setDefaultShallowSuspenseContext(suspenseStackCursor.current));
            }

            break;
          }

        case SuspenseListComponent:
          {
            var didSuspendBefore = (current?1.effectTag & DidCapture) !== NoEffect;

            var _hasChildWork = workInProgress.childExpirationTime >= renderExpirationTime;

            if (didSuspendBefore) {
              if (_hasChildWork) {
                // If something was in fallback state last time, and we have all the
                // same children then we're still in progressive loading state.
                // Something might get unblocked by state updates or retries in the
                // tree which will affect the tail. So we need to use the normal
                // path to compute the correct tail.
                return updateSuspenseListComponent(current?1, workInProgress, renderExpirationTime);
              } 
              // If none of the children had any work, that means that none of
              // them got retried so they'll still be blocked in the same way
              // as before. We can fast bail out.


              workInProgress.effectTag |= DidCapture;
            } 
            // If nothing suspended before and we're rendering the same children,
            // then the tail doesn't matter. Anything new that suspends will work
            // in the "together" mode, so we can continue from the state we had.


            var renderState = workInProgress.memoizedState;

            if (renderState !== null) {
              // Reset to the "together" mode in case we've started a different
              // update in the past but didn't complete it.
              renderState.rendering = null;
              renderState.tail = null;
            }

            pushSuspenseContext(workInProgress, suspenseStackCursor.current);

            if (_hasChildWork) {
              break;
            } else {
              // If none of the children had any work, that means that none of
              // them got retried so they'll still be blocked in the same way
              // as before. We can fast bail out.
              return null;
            }
          }
      }

      return bailoutOnAlreadyFinishedWork(current?1, workInProgress, renderExpirationTime);
    } else {
      // An update was scheduled on this fiber, but there are no new props
      // nor legacy context. Set this to false. If an update queue or context
      // consumer produces a changed value, it will set this to true. Otherwise,
      // the component will assume the children have not changed and bail out.
      //已计划在此fiber上进行更新,但没有新的 props、context,设置为false。
      //如果更新队列或 context consumer产生一个更改的值,它将设置为true。
      //以外,组件将假定children未更改并退出。
      didReceiveUpdate = false;
    }
  } else {
    didReceiveUpdate = false;
  } 
  
  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      {
        return mountIndeterminateComponent(current?1, workInProgress, workInProgress.type, renderExpirationTime);
      }

    case LazyComponent:
      {
        var elementType = workInProgress.elementType;
        return mountLazyComponent(current?1, workInProgress, elementType, updateExpirationTime, renderExpirationTime);
      }

    case FunctionComponent:
      {
        var _Component = workInProgress.type;
        var unresolvedProps = workInProgress.pendingProps;
        var resolvedProps = workInProgress.elementType === _Component ? unresolvedProps : resolveDefaultProps(_Component, unresolvedProps);
        return updateFunctionComponent(current?1, workInProgress, _Component, resolvedProps, renderExpirationTime);
      }

    case ClassComponent:
      {
        var _Component2 = workInProgress.type;
        var _unresolvedProps = workInProgress.pendingProps;

        var _resolvedProps = workInProgress.elementType === _Component2 ? _unresolvedProps : resolveDefaultProps(_Component2, _unresolvedProps);

        return updateClassComponent(current?1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
      }

    case HostRoot:
      return updateHostRoot(current?1, workInProgress, renderExpirationTime);

    case HostComponent:
      return updateHostComponent(current?1, workInProgress, renderExpirationTime);

    case HostText:
      return updateHostText(current?1, workInProgress);

    case SuspenseComponent:
      return updateSuspenseComponent(current?1, workInProgress, renderExpirationTime);

    case HostPortal:
      return updatePortalComponent(current?1, workInProgress, renderExpirationTime);

    case ForwardRef:
      {
        var type = workInProgress.type;
        var _unresolvedProps2 = workInProgress.pendingProps;

        var _resolvedProps2 = workInProgress.elementType === type ? _unresolvedProps2 : resolveDefaultProps(type, _unresolvedProps2);

        return updateForwardRef(current?1, workInProgress, type, _resolvedProps2, renderExpirationTime);
      }

    case Fragment:
      return updateFragment(current?1, workInProgress, renderExpirationTime);

    case Mode:
      return updateMode(current?1, workInProgress, renderExpirationTime);

    case Profiler:
      return updateProfiler(current?1, workInProgress, renderExpirationTime);

    case ContextProvider:
      return updateContextProvider(current?1, workInProgress, renderExpirationTime);

    case ContextConsumer:
      return updateContextConsumer(current?1, workInProgress, renderExpirationTime);

    case MemoComponent:
      {
        var _type2 = workInProgress.type;
        var _unresolvedProps3 = workInProgress.pendingProps; 
        // Resolve outer props first, then resolve inner props.

        var _resolvedProps3 = resolveDefaultProps(_type2, _unresolvedProps3);

        {
          if (workInProgress.type !== workInProgress.elementType) {
            var outerPropTypes = _type2.propTypes;

            if (outerPropTypes) {
              checkPropTypes(outerPropTypes, _resolvedProps3, // Resolved for outer only
              'prop', getComponentName(_type2), getCurrentFiberStackInDev);
            }
          }
        }

        _resolvedProps3 = resolveDefaultProps(_type2.type, _resolvedProps3);
        return updateMemoComponent(current?1, workInProgress, _type2, _resolvedProps3, updateExpirationTime, renderExpirationTime);
      }

    case SimpleMemoComponent:
      {
        return updateSimpleMemoComponent(current?1, workInProgress, workInProgress.type, workInProgress.pendingProps, updateExpirationTime, renderExpirationTime);
      }

    case IncompleteClassComponent:
      {
        var _Component3 = workInProgress.type;
        var _unresolvedProps4 = workInProgress.pendingProps;

        var _resolvedProps4 = workInProgress.elementType === _Component3 ? _unresolvedProps4 : resolveDefaultProps(_Component3, _unresolvedProps4);

        return mountIncompleteClassComponent(current?1, workInProgress, _Component3, _resolvedProps4, renderExpirationTime);
      }

    case SuspenseListComponent:
      {
        return updateSuspenseListComponent(current?1, workInProgress, renderExpirationTime);
      }

    case FundamentalComponent:
      {
        if (enableFundamentalAPI) {
          return updateFundamentalComponent$1(current?1, workInProgress, renderExpirationTime);
        }

        break;
      }

    case ScopeComponent:
      {
        if (enableScopeAPI) {
          return updateScopeComponent(current?1, workInProgress, renderExpirationTime);
        }

        break;
      }
  }

  {
    {
      throw Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in React. Please file an issue.");
    }
  }
}

源码说明

  1. 判断 didReceiveUpdate 的值
    • 如果 current?1 为空,直接设为 false;
    • 如果 current?1 不为空
      • props、context 或 type 值改变,设为 true;
      • 当 updateExpirationTime < renderExpirationTime 时,设为 false;此时根据 tag 类型,将 context 信息入栈;
      • 否则设为 false。
  2. workInProgress.expirationTime = NoWork;
  3. 根据 workInProgress.tag,判断执行哪种类型的更新,我们着重分析下面这八种类型:
    • HostRoot:updateHostRoot()
    • HostComponent:updateHostComponent()
    • HostText:updateHostText()
    • FunctionComponent:updateFunctionComponent()
    • ClassComponent:updateClassComponent()
    • MemoComponent:updateMemoComponent()
    • ContextProvider:updateContextProvider()
    • ContextConsumer:updateContextConsumer()
  4. 返回 workInProgress.child

beginWork 总是返回其子节点。

updateHostRoot

function updateHostRoot(current?1, workInProgress, renderExpirationTime) {
  pushHostRootContext(workInProgress);
  var updateQueue = workInProgress.updateQueue;

  if (!(updateQueue !== null)) {
    {
      throw Error("If the root does not have an updateQueue, we should have already bailed out. This error is likely caused by a bug in React. Please file an issue.");
    }
  }

  var nextProps = workInProgress.pendingProps;
  var prevState = workInProgress.memoizedState;
  var prevChildren = prevState !== null ? prevState.element : null;
  processUpdateQueue(workInProgress, updateQueue, nextProps, null, renderExpirationTime);
  var nextState = workInProgress.memoizedState; // Caution: React DevTools currently depends on this property
  // being called "element".

  var nextChildren = nextState.element;

  if (nextChildren === prevChildren) {
    // If the state is the same as before, that's a bailout because we had
    // no work that expires at this time.
    resetHydrationState();
    return bailoutOnAlreadyFinishedWork(current?1, workInProgress, renderExpirationTime);
  }

  var root = workInProgress.stateNode;

  if (root.hydrate && enterHydrationState(workInProgress)) {
    // If we don't have any current children this might be the first pass.
    // We always try to hydrate. If this isn't a hydration pass there won't
    // be any children to hydrate which is effectively the same thing as
    // not hydrating.
    var child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
    workInProgress.child = child;
    var node = child;

    while (node) {
      // Mark each child as hydrating. This is a fast path to know whether this
      // tree is part of a hydrating tree. This is used to determine if a child
      // node has fully mounted yet, and for scheduling event replaying.
      // Conceptually this is similar to Placement in that a new subtree is
      // inserted into the React tree here. It just happens to not need DOM
      // mutations because it already exists.
      node.effectTag = node.effectTag & ~Placement | Hydrating;
      node = node.sibling;
    }
  } else {
    // Otherwise reset hydration state in case we aborted and resumed another
    // root.
    reconcileChildren(current?1, workInProgress, nextChildren, renderExpirationTime);
    resetHydrationState();
  }

  return workInProgress.child;
}

源码说明

主要是3个函数

  1. pushHostRootContext
  2. processUpdateQueue
  3. reconcileChildren

最后返回 workInProgress.child。

pushHostRootContext

function pushHostRootContext(workInProgress) {
  var root = workInProgress.stateNode;

  if (root.pendingContext) {
    pushTopLevelContextObject(workInProgress, root.pendingContext, root.pendingContext !== root.context);
  } else if (root.context) {
    // Should always be set
    pushTopLevelContextObject(workInProgress, root.context, false);
  }

  pushHostContainer(workInProgress, root.containerInfo);
}

// ??

processUpdateQueue

这个函数主要处理update和updateQueue, 并根据update获取state

function processUpdateQueue(workInProgress, queue, props, instance, renderExpirationTime) {
  hasForceUpdate = false;
  queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);

  {
    currentlyProcessingQueue = queue;
  } 
  // These values may change as we process the queue.
  //当执行更新队列的时候,这些属性可能会动态改变,所以先创建副本变量
  var newBaseState = queue.baseState;
  var newFirstUpdate = null;
  var newExpirationTime = NoWork; 
  
  // Iterate through the list of updates to compute the result.
  //遍历更新列表以计算结果。 
  var update = queue.firstUpdate;
  var resultState = newBaseState;

  while (update !== null) {
    var updateExpirationTime = update.expirationTime;

    if (updateExpirationTime < renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      if (newFirstUpdate === null) {
        // This is the first skipped update. It will be the first update in
        // the new list.
        newFirstUpdate = update; 
        // Since this is the first update that was skipped, the current result
        // is the new base state.

        newBaseState = resultState;
      } // Since this update will remain in the list, update the remaining
      // expiration time.


      if (newExpirationTime < updateExpirationTime) {
        newExpirationTime = updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority.
      // Mark the event time of this update as relevant to this render pass.
      //此更新确实具有足够的优先级。
      //将此更新的事件时间标记为与此渲染过程相关。 
      // TODO: This should ideally use the true event time of this update rather than
      // its priority which is a derived and not reverseable value.
      // TODO: We should skip this update if it was already committed but currently
      // we have no way of detecting the difference between a committed and suspended
      // update here.
      markRenderEventTimeAndConfig(updateExpirationTime, update.suspenseConfig); 
      // Process it and compute a new result.
      //处理并计算新结果。 
      resultState = getStateFromUpdate(workInProgress, queue, update, resultState, props, instance);
      var callback = update.callback;

      if (callback !== null) {
        workInProgress.effectTag |= Callback; // Set this to null, in case it was mutated during an aborted render.

        update.nextEffect = null;

        if (queue.lastEffect === null) {
          queue.firstEffect = queue.lastEffect = update;
        } else {
          queue.lastEffect.nextEffect = update;
          queue.lastEffect = update;
        }
      }
    } // Continue to the next update.


    update = update.next;
  } // Separately, iterate though the list of captured updates.


  var newFirstCapturedUpdate = null;
  update = queue.firstCapturedUpdate;

  while (update !== null) {
    var _updateExpirationTime = update.expirationTime;

    if (_updateExpirationTime < renderExpirationTime) {
      // This update does not have sufficient priority. Skip it.
      if (newFirstCapturedUpdate === null) {
        // This is the first skipped captured update. It will be the first
        // update in the new list.
        newFirstCapturedUpdate = update; 
        // If this is the first update that was skipped, the current result is
        // the new base state.

        if (newFirstUpdate === null) {
          newBaseState = resultState;
        }
      } // Since this update will remain in the list, update the remaining
      // expiration time.


      if (newExpirationTime < _updateExpirationTime) {
        newExpirationTime = _updateExpirationTime;
      }
    } else {
      // This update does have sufficient priority. Process it and compute
      // a new result.
      resultState = getStateFromUpdate(workInProgress, queue, update, resultState, props, instance);
      var _callback = update.callback;

      if (_callback !== null) {
        workInProgress.effectTag |= Callback; // Set this to null, in case it was mutated during an aborted render.

        update.nextEffect = null;

        if (queue.lastCapturedEffect === null) {
          queue.firstCapturedEffect = queue.lastCapturedEffect = update;
        } else {
          queue.lastCapturedEffect.nextEffect = update;
          queue.lastCapturedEffect = update;
        }
      }
    }

    update = update.next;
  }

  if (newFirstUpdate === null) {
    queue.lastUpdate = null;
  }

  if (newFirstCapturedUpdate === null) {
    queue.lastCapturedUpdate = null;
  } else {
    workInProgress.effectTag |= Callback;
  }

  if (newFirstUpdate === null && newFirstCapturedUpdate === null) {
    // We processed every update, without skipping. That means the new base
    // state is the same as the result state.
    newBaseState = resultState;
  }

  queue.baseState = newBaseState;
  queue.firstUpdate = newFirstUpdate;
  queue.firstCapturedUpdate = newFirstCapturedUpdate; // Set the remaining expiration time to be whatever is remaining in the queue.
  // This should be fine because the only two other things that contribute to
  // expiration time are props and context. We're already in the middle of the
  // begin phase by the time we start processing the queue, so we've already
  // dealt with the props. Context in components that specify
  // shouldComponentUpdate is tricky; but we'll have to account for
  // that regardless.

  markUnprocessedUpdateTime(newExpirationTime);
  workInProgress.expirationTime = newExpirationTime;
  workInProgress.memoizedState = resultState;

  {
    currentlyProcessingQueue = null;
  }
}

源码说明

  1. 首先,调用 ensureWorkInProgressQueueIsAClone,确定是在副本 updateQueue 上操作;
  2. 然后
    • 通过 update.next 分别循环遍历一次 updateQueue 中的所有 update 和 capturedUpdate;
    • 对于每个 update,调用 getStateFromUpdate,计算新的 resultState;
    • 当 update 上有 callback 时(如 this.setState({a:1},()=>{}) 的回调函数),设置 workInProgress.effectTag |= Callback,并将该 update 追加到 effect 链表的最后。待更新 state 后,再执行 callback;
  3. 最后,更新 queue 和 workInProgress。重点在于用 resultState 更新 queue.baseState、workInProgress.memoizedState。

ensureWorkInProgressQueueIsAClone

function ensureWorkInProgressQueueIsAClone(workInProgress, queue) {
  var current = workInProgress.alternate;

  if (current !== null) {
    // If the work-in-progress queue is equal to the current queue,
    // we need to clone it first.
    if (queue === current.updateQueue) {
      queue = workInProgress.updateQueue = cloneUpdateQueue(queue);
    }
  }

  return queue;
}

源码说明

  • 确保 workInProgress 上的 updateQueue 是 workInProgress.alternate.updateQueue 的副本。

getStateFromUpdate

function getStateFromUpdate(workInProgress, queue, update, prevState, nextProps, instance) {
  switch (update.tag) {
    case ReplaceState:
      {
        var payload = update.payload;

        if (typeof payload === 'function') {
          // Updater function
          {
            enterDisallowedContextReadInDEV();

            if (debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) {
              payload.call(instance, prevState, nextProps);
            }
          }

          var nextState = payload.call(instance, prevState, nextProps);

          {
            exitDisallowedContextReadInDEV();
          }

          return nextState;
        } // State object


        return payload;
      }

    case CaptureUpdate:
      {
        workInProgress.effectTag = workInProgress.effectTag & ~ShouldCapture | DidCapture;
      }
    // Intentional fallthrough

    case UpdateState:
      {
        var _payload = update.payload;
        var partialState;

        if (typeof _payload === 'function') {
          // Updater function
          {
            enterDisallowedContextReadInDEV();

            if (debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) {
              _payload.call(instance, prevState, nextProps);
            }
          }

          partialState = _payload.call(instance, prevState, nextProps);

          {
            exitDisallowedContextReadInDEV();
          }
        } else {
          // Partial state object
          partialState = _payload;
        }

        if (partialState === null || partialState === undefined) {
          // Null and undefined are treated as no-ops.
          return prevState;
        } // Merge the partial state and the previous state.


        return _assign({}, prevState, partialState);
      }

    case ForceUpdate:
      {
        hasForceUpdate = true;
        return prevState;
      }
  }

  return prevState;
}

源码说明

根据 update.tag 判断如何更新 state:

  • ReplaceState:如果 update.payload 是函数,将该函数的返回值作为 nextState;否则直接用 payload 作为 nextState;
  • CaptureUpdate:将 workInProgress.effectTag 设为 DidCapture;
  • UpdateState:先根据 update.payload 是否是函数,计算 partialState;然后使用 _assign({}, prevState, partialState) 将 state 合并;

reconcileChildren

// 参数说明:nextChildren 就是 processUpdateQueue 计算得到的 nextState 上的 element
function reconcileChildren(current?1, workInProgress, nextChildren, renderExpirationTime) {
  if (current?1 === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.
    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    workInProgress.child = reconcileChildFibers(workInProgress, current?1.child, nextChildren, renderExpirationTime);
  }
}

源码说明

  • 根据当前 fiber 是否存在,判断是 mount,还是 reconcile。

ChildReconciler

简略结构如下:

function ChildReconciler(shouldTrackSideEffects) {
  function deleteChild(){}
  function deleteRemainingChildren(){}
  function mapRemainingChildren(){}
  function useFiber(){}
  function placeChild(){}
  function placeSingleChild(){}
  function updateTextNode(){}
  function updateElement(){}
  function updatePortal(){}
  function updateFragment(){}
  function createChild(){}
  function updateSlot(){}
  function updateFromMap(){}
  function warnOnInvalidKey(){}
  function reconcileChildrenArray(){}
  function reconcileChildrenIterator(){}
  function reconcileSingleTextNode(){}
  function reconcileSingleElement(){}
  function reconcileSinglePortal(){}
  function reconcileChildFibers(){}

  return reconcileChildFibers;
}

var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

源码说明

  • ChildReconciler 返回一个函数:reconcileChildFibers;
  • mountChildFibers 和 reconcileChildFibers 都是调用 ChildReconciler,只不过传的值分别是 false 和 true。

reconcileChildFibers

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) {
  // 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.
  // Handle top level unkeyed fragments as if they were arrays.
  // This leads to an ambiguity between <>{[...]}</> and <>...</>.
  // We treat the ambiguous cases above the same.
  var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;

  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  } // Handle object types


  var isObject = typeof newChild === 'object' && newChild !== null;

  if (isObject) {
    switch (newChild.?typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime));

      case REACT_PORTAL_TYPE:
        return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime));
    }
  }

  if (typeof newChild === 'string' || typeof newChild === 'number') {
    return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, expirationTime));
  }

  if (isArray(newChild)) {
    return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, expirationTime);
  }

  if (getIteratorFn(newChild)) {
    return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, expirationTime);
  }

  if (isObject) {
    throwOnInvalidObjectType(returnFiber, newChild);
  }

  {
    if (typeof newChild === 'function') {
      warnOnFunctionType();
    }
  }

  if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
    // If the new child is undefined, and the return fiber is a composite
    // component, throw an error. If Fiber return types are disabled,
    // we already threw above.
    switch (returnFiber.tag) {
      case ClassComponent:
        {
          {
            var instance = returnFiber.stateNode;

            if (instance.render._isMockFunction) {
              // We allow auto-mocks to proceed as if they're returning null.
              break;
            }
          }
        }
      // Intentionally fall through to the next case, which handles both
      // functions and classes
      // eslint-disable-next-lined no-fallthrough

      case FunctionComponent:
        {
          var Component = returnFiber.type;

          {
            {
              throw Error((Component.displayName || Component.name || 'Component') + "(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.");
            }
          }
        }
    }
  } // Remaining cases are all treated as empty.


  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

源码说明

  • 根据 newChild 的不同类型,执行 ChildReconciler 中不同的 reconcile,详情可见流程图:

后续逐个分析每个 reconcile 函数。