目录
2021SC@SDUSC
commit阶段的改动
在这里想分析一下commit阶段,这里指出一个小问题,事实上react最新版本中已经对effectList做了移除,并不寻找更新节点,而是遍历整棵Fiber树,用subtreeTag变量进行优化,详情参照
https://github.com/facebook/react/pull/20595
相当一部分资料介绍了effectList链(这就是一个需要更新的Fiber链表),但现在react的底层架构已经无法看到该变量了。顺便一提,我在这里试图参考其他人的分析,结果发现大部分都在谈effectList,假如react没有出新版本再把effectList加回来的话,那就说明他们解析的版本比较旧了。
这里要结合一下以前说过的调度原理,此时的commit任务是同步执行的,也就是说,此时的更新应当一次完成。
commit的三个阶段
在commit阶段,此时的Fiber节点已经做好标记,可以进行相应的处理以映射在DOM树上,将优先级设为syncLane,优先级最高,通过commitRoot找到commitRootImpl,在此时异步调用所有的effect,然后进入commitBeforeMutationEffects调用commitBeforeMutationEffects_begin,再调用commitBeforeMutationEffects_complete又到commitBeforeMutationEffectsOnFiber方法,终于得到stateNode的getSnapshotBeforeUpdate(这也是react中的生命周期函数,在commit之前得到以前的函数与信息),从而得到类组件中DOM信息的改动并且存储该信息。
接下来通过commitMutationEffects方法对DOM节点进行改动。包括节点的插入,删除以及更新。
现在调用commitLayoutEffects方法,和BeforeMutation对称,调用commitLayoutEffects_begin再调用commitLayoutMountEffects_complete又到commitLayoutEffectOnFiber方法。
谈谈 BeforeMutation阶段
正如上文中所提到的,before mutation阶段中会调度类组件中的 getSnapshotBeforeUpdate以及异步调度函数组件中的useEffect。在这里说一下调用该生命周期函数的位置,位于commitBeforeMutationEffects_complete的commitBeforeMutationEffectsOnFiber中,在这里将Fiber节点的alternate属性赋值给current,代表以前的节点,然后对其进行分类分析,假如current为类组件的话,就会将memoizedProps(赋值给prevProps),memoizedState(赋值给prevState)以及Fiber节点中的stateNode取出(注意,在commit阶段中,stateNode已经更新完成,所以可以作为实例),接下来在该实例中调用 getSnapshotBeforeUpdate方法,并将 prevProps 与 prevState 作为以前的状态作为参数传入。
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if (enableCreateEventHandleAPI) {
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// Check to see if the focused element was inside of a hidden (Suspense) subtree.
// TODO: Move this out of the hot path using a dedicated effect tag.
if (
finishedWork.tag === SuspenseComponent &&
isSuspenseBoundaryBeingHidden(current, finishedWork) &&
doesFiberContain(finishedWork, focusedInstanceHandle)
) {
shouldFireAfterActiveInstanceBlur = true;
beforeActiveInstanceBlur(finishedWork);
}
}
}
if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(finishedWork);
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
break;
}
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
if (__DEV__) {
if (
finishedWork.type === finishedWork.elementType &&
!didWarnAboutReassigningProps
) {
if (instance.props !== finishedWork.memoizedProps) {
console.error(
'Expected %s props to match memoized props before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.props`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);
}
if (instance.state !== finishedWork.memoizedState) {
console.error(
'Expected %s state to match memoized state before ' +
'getSnapshotBeforeUpdate. ' +
'This might either be because of a bug in React, or because ' +
'a component reassigns its own `this.state`. ' +
'Please file an issue.',
getComponentNameFromFiber(finishedWork) || 'instance',
);
}
}
}
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
if (__DEV__) {
const didWarnSet = ((didWarnAboutUndefinedSnapshotBeforeUpdate: any): Set<mixed>);
if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
didWarnSet.add(finishedWork.type);
console.error(
'%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' +
'must be returned. You have returned undefined.',
getComponentNameFromFiber(finishedWork),
);
}
}
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:
// Nothing to do for these component types
break;
default: {
invariant(
false,
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',
);
}
}
resetCurrentDebugFiberInDEV();
}
}
谈谈LayOut阶段
关于LayOut阶段在Fiber节点的所做的处理,这里简要说明一下在commitLayoutMountEffects_complete中所调用的commitLayoutEffectOnFiber方法。在这其中函数组件调用useLayoutEffect并得到结果,而useEffect则再次进行调度(beforeMutation也进行了异步调度),在这里就体现出了useEffect与useLayoutEffect的不同,重点在运行时机上。而类组件调用了commitUpdateQuque来处理更新队列,还调用了生命函数componentDidMount或componentDidUpdate,这取决与fiber节点中的alternate属性,假如为null,代表挂载,假如有值,代表更新,另外在 componentDidUpdate中会传入三个参数,分别是prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate(作为getSnapshotBeforeUpdate的结果传入)。至此commit阶段完成。在这里记录了一下react渲染过程中的种种具体流程与工作逻辑。能够对react有更清晰的理解与认识。
总结
介绍了commit阶段的新版本源码的改动及其相应的三个阶段(beforeMutation,Mutation,layout)。同时简明扼要的介绍了 beforeMutation与layout阶段的部分具体代码。
关于mutation阶段的具体流程,我将会在下一篇博客中介绍。