目录
关于bailoutOnAlreadyFinishedWork
2021SC@SDUSC
beginWork的执行过程
谈谈beginWork,react所需要调度的就是Fiber树的构建与更新,所以我们从beginWork开始,它被从ReactFiberBeginWork.new.js中引入,而在beginWork中会比较current树中的节点与workinprogress树中的节点,判断是否需要更新或者挂载,如果需要挂载或者是无法进行复用子节点的更新就对当前的子节点进行标记并返回新子节点,否则执行bailoutOnAlreadyFinishedWork复用,假如函数返回子节点(说明子节点需要对后代进行处理,用childlanes判断),就继续处理子节点,假如返回值为null,那就说明处理已完成(看来,Fiber树的遍历是深度遍历,一直遍历到子节点,这里面的current树为渲染后的树,而workInProgress树为新构建的树)。
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
if (__DEV__) {
if (workInProgress._debugNeedsRemount && current !== null) {
// This will restart the begin phase with a new fiber.
return remountFiber(
current,
workInProgress,
createFiberFromTypeAndProps(
workInProgress.type,
workInProgress.key,
workInProgress.pendingProps,
workInProgress._debugOwner || null,
workInProgress.mode,
workInProgress.lanes,
),
);
}
}
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// 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 {
// Neither props nor legacy context changes. Check if there's a pending
// update or context change.
const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
current,
renderLanes,
);
if (
!hasScheduledUpdateOrContext &&
// If this is the second pass of an error or suspense boundary, there
// may not be work scheduled on `current`, so we check for this flag.
(workInProgress.flags & DidCapture) === NoFlags
) {
// No pending updates or context. Bail out now.
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(
current,
workInProgress,
renderLanes,
);
}
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
// This is a special case that only exists for legacy mode.
// See https://github.com/facebook/react/pull/19216.
didReceiveUpdate = true;
} 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.
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
// 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.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
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,
renderLanes,
);
}
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,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
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,
renderLanes,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderLanes);
case Mode:
return updateMode(current, workInProgress, renderLanes);
case Profiler:
return updateProfiler(current, workInProgress, renderLanes);
case ContextProvider:
return updateContextProvider(current, workInProgress, renderLanes);
case ContextConsumer:
return updateContextConsumer(current, workInProgress, renderLanes);
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',
getComponentNameFromType(type),
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
renderLanes,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
renderLanes,
);
}
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,
renderLanes,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(current, workInProgress, renderLanes);
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(current, workInProgress, renderLanes);
}
break;
}
case OffscreenComponent: {
return updateOffscreenComponent(current, workInProgress, renderLanes);
}
case LegacyHiddenComponent: {
return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
}
case CacheComponent: {
if (enableCache) {
return updateCacheComponent(current, workInProgress, renderLanes);
}
break;
}
}
invariant(
false,
'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
'React. Please file an issue.',
workInProgress.tag,
);
}
所以beginWork方法的主要任务就是判断当前节点是否需要更新并且且进行标记,当然,如果是挂载节点或者更新但无法复用的节点就返回新建节点。复用节点需要判断子节点中是否有节点需要进行处理,如果有就返回子节点,没有就处理完成。
谈谈其中的updateClassComponent方法
首先会对其中的context进行处理,调用isLegacyContextProvider方法查看组件中是否有context,有则调用pushLegacyContextProvider方法来将其制作为contextProvider。然后在根据workInProgress中的stateNode来更新instance变量,再次分为多种情况,如果instance并非空值,而且current也并非空值,那么可以直接调用updateClassInstance方法来对实例进行更新,在 updateClassInstance 中,主要是对instance进行复用,更新props与state,并且调用其中的生命周期函数,这其中主要包括componentWillUpdate,componentDidUpdate以及 getDerivedStateFromProps ,(顺便一提,其实在新版本的react中 componentWillUpdate 已经并非生命周期函数了,统一使用 getDerivedStateFromProps )在更新props与state的过程中,首先对相关的生命周期函数进行处理,比如getDerivedStateFromProps等,其含义为从props中获取派生状态,然后将更新的props与state赋值给instance,当然也会返回shouldUpdate来表明是否需要更新实例。
如果instance为空值,当current不为空值,此时workInProgress中的flags应当改为Placement,因为此时的workInProgress是一个新的Fiber节点,理应在以后的Fiber树构建过程中插入。然后根据current的值来挂载classComponent中的instance,此时需要调用mountClassInstance,从而对其中的实例进行挂载。首先会更新其中的更新队列,将其作为一个更新事件处理,接下来读取workInProgress中的context以及state部分并且将其赋值给instance变量,另外,此时需要处理的生命周期函数为getDerivedStateFromProps以及componentDidMount等生命周期函数。如果instance不为空值而current为空值,那么可以调用resumeMountClassInstance来对已经挂载的实例进行重用,在 resumeMountClassInstance 中,主要包括调用 componentDidMount 等生命周期函数,当然此时同样需要判断instance中的props以及state的更新,另外,此时同样需要返回shouldUpdate。
之后根据finishClassComponent方法提供nextUnitOfWork也就是调用下一个节点,而在 finishClassComponent 中,首先对当前进入的classComponent的Refs进行更新,之后根据workInPrgress中是否含有DidCapture来对错误进行处理。该节点是否为处理过程中需要重新处理的节点直接关系到下一个节点的选取,关于这点,我会在以后的错误边界机制中做进一步的讲解。另外,假如不需要更新而且也并没有错误出现,那么就会探测组件中是否含有context,有就对其是否能成为context provider进行辨别,然后执行bailoutOnAlreadyFinishedWork,假如需要更新,那就先对其子节点进行协调,调用reconcileChildren方法,此时应当将workInProgress节点的memoizedState赋值为上文所提到的instance变量的state,并且再次判断是否能够成为context provider,由于reconcileChildren已经调整了Fiber树的结构,所以此时可以返回workInProgress中的child节点作为nextUnitOfWork,也就是要处理的下一个节点。
这就是beginWork中的updateClasscomponent方法的大致作用与流程。
关于bailoutOnAlreadyFinishedWork
会对子节点的Lane进行判断,假如不在目标区间内就会返回null值说明更新完成。假如在区间内说明要继续处理,调用cloneChildFibers根据workInProgress子节点(在这种情况下由于尚未对节点进行更新,所以也就是current中的子节点)以创建新的子节点并且返回。
DIFF算法的入口
接下来以更新中的updateClassComponent为例,假如判断workInProgress中的标签为classComponent,在updateClassComponent中通过finishClassComponent执行reconcileChildren(其中用DIFF算法更新),这就是DIFF算法的入口,下一篇博客我会对DIFF算法进行讲解。
总结
在这里介绍了的beginWork功能以及梳理了一下从beginWork到DIFF算法的大致流程,并且还介绍了updateClassComponent的具体逻辑与功能以及 bailoutOnAlreadyFinishedWork 函数的作用。通过beginWork,此时workinProgress树中的Fiber节点已经完成了更新的一部分流程,在对节点的更新队列的处理中(关于更新队列以及更新任务的介绍,我会在以后进行补充),节点得到了新状态。并且还通过DIFF(这是下一篇博客的主要内容)更新了workInProgress节点树的结构。