React源码学习进阶(五)beginWork如何处理Fiber
本文采用React v16.13.1版本源码进行分析
源码解析
beginWork的代码位于packages/react-reconciler/src/ReactFiberBeginWork.js:
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime;
// Before entering the begin phase, clear pending update priority.
// TODO: This assumes that we're about to evaluate the component and process
// the update queue. However, there's an exception: SimpleMemoComponent
// sometimes bails out later in the begin phase. This indicates that we should
// move this assignment out of the common path and into each branch.
workInProgress.expirationTime = NoWork;
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderExpirationTime,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderExpirationTime,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
case FundamentalComponent: {
if (enableFundamentalAPI) {
return updateFundamentalComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case Block: {
if (enableBlocksAPI) {
const block = workInProgress.type;
const props = workInProgress.pendingProps;
return updateBlock(
current,
workInProgress,
block,
props,
renderExpirationTime,
);
}
break;
}
}
}实际上beginWork就是一个很大的分发入口,根据tag的不同来分发处理不同的逻辑,对于rootFiber来说,它的tag类型是HostRoot,因此我们先核心聚焦于HostRoot的实现:
我们先忽略前面对于context和updateQueue的处理,实际上这里只有一个核心逻辑:
调用
reconcileChildren返回
workInProgress.child
而实际上workInProgress.child正是reconciileChildren方法被赋值:
实际上挂载的场景执行的是reconcileChildFibers,这个实现在packages/react-reconciler/src/ReactChildFiber.js:
实际上这里是根据elementType来进行不同的处理,我们常规来说应该是进入到reconcileSingleElement分支:
这里其实就是调用createFiberFromElement创建一个fiber节点返回,而这个节点正是rootFiber的子节点。
我们先回想一下,这个过程实际上伴随着这个return也就结束了,也就是在这个流程中,我们为rootFiber添加了一个child节点,形成链表结构,然后返回,到我们最开始的循环:
实际上,针对子节点,又会进入到performUnitOfWork流程中,继续执行beginWork流程。
也就是说,这个loop其实是在不断地构建整个Fiber树,同时在遍历处理每个Fiber节点的过程。
接下来处理的Fiber节点是个Mode节点,因为我们调用了React.StrictMode组件,对于这个节点的处理非常简单:
实际上它什么都没有做,就是继续递归执行reconcileChildren,不过它已经被插入到fiber树当中了,这个很重要。
要注意的是,这里的fiber其实是没有current的,实际上我们新创建的节点,并没有赋值alternate属性,自然也只有workInProgress而没有current,在没有current的情况下,reconcileChildren会走到mountChildFibers分支:
实际上这两个分支的区别只是在于传参的不同,这个参数是shouldTrackSideEffects,当我们调用mountChildFibers时,这个参数是false,如果是reconcile,则这个值是true,这一点我们先记下。
接下来执行的节点是App,它被标记成一个IndeterminateComponent,为什么会是这个组件呢?让我们看看createFiberFromElement的逻辑:
这里没什么特殊的处理,最终转化到createFiberFromTypeAndProps:
从这里的逻辑就可以看到,如果我们正常一个组件进来创建Fiber的时候,默认就会标记为IndeterminateComponent,类组件会被标记为ClassComponent.
接着我们看一下这个组件的update流程:
这里核心做了两件事情:
调用
renderWithHooks对当前组件进行reconcile,拿到子节点标记
Fiber节点为FunctionComponent,启动子节点的递归reconcileChildren
可以看到,针对IndeterminateComponent来说,一开始是不太明确它到底是个什么组件的,执行完了之后才赋值的FunctionComponent,这是因为类组件可以不使用类来实现(通过函数返回一个对象来实现),这是一个小众用法。
接下来我们简单看一下renderWithHooks的逻辑:
我们先忽略一下这里针对hooks的处理逻辑,其实可以简单的理解这里就是执行了函数组件,拿到了children,然后返回。
接着就是到了叶子组件HostComponent的更新流程了:
可以看到这里基本上没有什么处理逻辑,直接进入到reconcileChildren流程了,接下来的child子节点可能是一个数组:
从之前的分析就可以看出来,这块也是实现diff算法的精华所在,在这里我们先关注首次挂载的逻辑,会进到这个循环中:
这段代码做了三件事:
调用
createChild为每个子元素创建一个fiber调用
placeChild确定放置的位置创建链表结构,
sibling指向下一个兄弟节点
其中,placeChild逻辑如下:
上面提到了shouldTrackSideEffects在首次挂载的情况下是false,因此在挂载流程中这里是不执行任何逻辑的。
最后返回第一个child节点,这个单元逻辑就已经执行完成。
当下一个节点为空时,就会走到completeUnitOfWork的流程,这个流程下篇文章我们进行详细讲解。
至此,一次beginWork的深度递归就已经结束了。
小结一下
这次我们分析了一次beginWork递归的流程,其实beginWork就是一个用来分发和创建fiber树结构的入口,我们目前遇到了几类更新方法:
mountIndeterminateComponent,用来更新函数组件updateHostRoot,用来更新根节点updateHostComponent,用来更新DOM组件updateMode用来更新Mode
在整个递归过程中,我们创建了如下的Fiber树:

Last updated