本文基于 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.");
}
}
}
源码说明
- 判断 didReceiveUpdate 的值
- 如果 current?1 为空,直接设为 false;
- 如果 current?1 不为空
- props、context 或 type 值改变,设为 true;
- 当 updateExpirationTime < renderExpirationTime 时,设为 false;此时根据 tag 类型,将 context 信息入栈;
- 否则设为 false。
- workInProgress.expirationTime = NoWork;
- 根据 workInProgress.tag,判断执行哪种类型的更新,我们着重分析下面这八种类型:
- HostRoot:updateHostRoot()
- HostComponent:updateHostComponent()
- HostText:updateHostText()
- FunctionComponent:updateFunctionComponent()
- ClassComponent:updateClassComponent()
- MemoComponent:updateMemoComponent()
- ContextProvider:updateContextProvider()
- ContextConsumer:updateContextConsumer()
- 返回 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个函数
- pushHostRootContext
- processUpdateQueue
- 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;
}
}
源码说明
- 首先,调用 ensureWorkInProgressQueueIsAClone,确定是在副本 updateQueue 上操作;
- 然后
- 通过 update.next 分别循环遍历一次 updateQueue 中的所有 update 和 capturedUpdate;
- 对于每个 update,调用 getStateFromUpdate,计算新的 resultState;
- 当 update 上有 callback 时(如 this.setState({a:1},()=>{}) 的回调函数),设置 workInProgress.effectTag |= Callback,并将该 update 追加到 effect 链表的最后。待更新 state 后,再执行 callback;
- 最后,更新 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 函数。