createWorkInProgress创建workInPogress Fiber或者复用已有的current Fiber节点
初次渲染
首次执行reactDom.render时,会创建整个应用的根节点,然后FiberRootNode的current指向rootFiber当前应用的根节点。
双缓存机制-首屏渲染的逻辑中,会基于current rootFiber 建立workInProgress rootFiber 。
首屏渲染中,第一次进入createWorkInProgress的current是div #root,还current Fiber没有alternate,此时workInProgress Fiber为null。
createFiber 创建一个新的fiber节点,并将current Fiber的参数都赋值给workInProgress Fiber,并返回workInProgress Fiber
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
if (workInProgress === null) {
//createFiber 创建一个新的fiber节点
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
//将current Fiber的参数都赋值给workInProgress Fiber
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
...
//将current指向workInProgress的alternate
workInProgress.alternate = current;
//将workInProgress指向current.alternate
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps; // Needed because Blocks store data on type.
workInProgress.type = current.type; // We already have an alternate.
// Reset the effect tag.
workInProgress.flags = NoFlags; // The effects are no longer valid.
workInProgress.subtreeFlags = NoFlags;
workInProgress.deletions = null;
{
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
} // Reset all effects except static ones.
// Static effects are not specific to a render.
//将workInProgress参数赋值为current参数。
workInProgress.flags = current.flags & StaticMask;
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue; // Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
var currentDependencies = current.dependencies;
workInProgress.dependencies = currentDependencies === null ? null : {
lanes: currentDependencies.lanes,
firstContext: currentDependencies.firstContext
}; // These will be overridden during the parent's reconciliation
//将current.sibling赋值workInProgress的sibling等等
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
{
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
{
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
switch (workInProgress.tag) {
case IndeterminateComponent:
case FunctionComponent:
case SimpleMemoComponent:
workInProgress.type = resolveFunctionForHotReloading(current.type);
break;
case ClassComponent:
workInProgress.type = resolveClassForHotReloading(current.type);
break;
case ForwardRef:
workInProgress.type = resolveForwardRefForHotReloading(current.type);
break;
}
}
return workInProgress;
} // Used to reuse a Fiber for a second pass.
在beginWork时,依次执行,执行到div时,由于div是一个HostComponent
执行reconcileChildren前,workInProgress为null
执行完,此时workInProgress.child就是一个新的Fiber节点
执行完reconcileChildren后,将div就有了header节点
整个render执行完之后,就进行commit,将FiberRootNode的current执行workInProgress的RootFiber上,这样workInprogress Fiber树就变成了current Fiber树。
第一次更新
点击p标签,更新时,current指向的是右边的rootFiber,前文中,current.alternate指向的是左边workInProgress的rootFiber。因此workInProgress rootFiber不为null,会复用workInProgress节点。
以app下的子节点div举例,初次渲染是,不存在current节点,但是更新时,同时存在current与workInProgress节点。
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
当执行到div这个节点时,workInProgress的rootFiber、APP节点已经创建完成。
执行reconcileChildFibers,创建workInProgress div的child时,会复用左边current的一些属性,包括header子节点。因此workInProgress div有了自己的子节点,当执行reconcileChildren时,header会创建自己的子节点。
当所有的都渲染完成以后,FiberRootNode的current指针,指向左边的rootFiber。
第二次更新
第二次更新与第一次的区别?
根据流程图,第二次更新时,每一个current节点,都已经存在了对应的workInProgress的alternate指针。(这就是双缓存机制的原理)
“递”阶段update流程 -beginWork
第一个进入beginWork的tag为3,是rootFIber
function beginWork(current, workInProgress, renderLanes) {
{
...
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
//根据新旧props,context,type是否相同,来判断这个节点是否有变化,
if (oldProps !== newProps || hasContextChanged() || ( // Force a re-render if the implementation changed due to hot reload:
workInProgress.type !== current.type )) {
// 标记这个节点是否有变化
didReceiveUpdate = true;
} else {
//此段代码表示本次更新中是否有任务,当前rootFiber没有任务,因此didReceiveUpdate为false,
//并进入attemptEarlyBailoutIfNoScheduledUpdate,不再像首屏渲染时,进入Fiber节点的Update逻辑
var 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;
...
}
....//update逻辑
}
}
当没有任务需要执行时,会直接进入bailoutOnAlreadyFinishedWork,克隆一份workInProgress的child
attemptEarlyBailoutIfNoScheduledUpdate
当前rootFiber没有任务,因此didReceiveUpdate为false,并进入attemptEarlyBailoutIfNoScheduledUpdate,不再像首屏渲染时,进入Fiber节点的Update逻辑
bailoutOnAlreadyFinishedWork
最终进入bailoutOnAlreadyFinishedWork方法
cloneChildFibers
createWorkInProgress
createWorkInProgress创建新的或者复用已有的currentFiber节点,并将这个child作为workInProgress的child。
有任务执行时
继续执行beginWork,当App进入时,跳过attemptEarlyBailoutIfNoScheduledUpdate,继续往下执行
进入updateFunctionComponent的逻辑
updateFunctionComponent
在updateFunctionComponent会调用renderWithHooks的方法
renderWithHooks
会调用Component方法,执行App(),进入App 代码的逻辑,返回jsx对象
此时的child已经返回了App 的jsx对象。
继续执行,renderWithHooks返回给nextChild
reconcileChildren:返回workInProgress的child,基于当前workInProgress Fiber与nextChild的子jsx对象形成一个新的WorkInProgress Fiber。
reconcileChildren
reconcileChildFibers
placeSingleChild:首屏渲染shouldTrackSideEffects为false,更新时为true。
此次更新不会进入里面,因为newFiber,即workInProgress的alternate不为null
什么情况下workInProgress.alternate为null?
由于workInProgress的alternate指向的是current的alternate,而current.alternate为null,表示当前fiber节点在上一个更新中不存在Fiber节点,是在本次更新中新创建的Fiber节点,所以要将新创建的Fiber节点的dom挂载在页面中。
“递”阶段mount与update的区别?
1、在beginWorkwork开始时,中有一个优化的逻辑,如果命中优化的逻辑,会进入bailoutOnAlreadyFinishedWork,克隆一份workInProgress的child。
2、如果没有命中,会继续走不同Fiber节点的Update的逻辑,在Update的逻辑中进入reconcile的逻辑(
reconcileChildren),在这个逻辑中,会将current的Fiber节点与nextChild的Jsx做对比,产生一个新的Fiber节点。