react源码debugger-commit阶段的完成

Posted coderlin_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码debugger-commit阶段的完成相关的知识,希望对你有一定的参考价值。

commit阶段

上节讲到了rootFiber完成completeWork的时候,返回了一个状态,为RootInCompleted,表示工作完成。调用finishConcurrentRender方法,该方法会调用commitRoot,开启commit阶段。

现在回顾一下,我们的fiber结构是

// App组件
const App: React.FC = () => 
  return <DD />;
;

// DD组件
class DD extends Component 
  render() 
    return <div>123</div>;
  

那么fiber结构应该是

rootFiber.child => App fiber
App fiber.child => DD fiber
DD fiber.child => div fiber

看看finishConcurrentRender方法

//完成了render阶段之后,开启commit阶段
finishConcurrentRender(root, exitStatus, lanes);

root是FiberRoot,exitStatus是RootInCompleted,表示完成状态。

finishConcurrentRender

function finishConcurrentRender(root, exitStatus, lanes) 
    switch(exitStatus)
        case RootInProgress:
        case RootFatalErrored: throw new Error('Root did not complete. This is a bug in React.')
        case RootCompleted: commitRoot(root, workInProgressRootRecoverableErrors); break;
        ...
    

可以看到,finishConcurrentRender主要就是完成了对exitStatus的判断,如果状态不对就抛出错误。然后调用commitRoot方法,开启commit阶段。

commitRoot

commitRoot会调用commitRootImpl方法,该方法时commit阶段的主要方法。

commitRootImpl方法

commitRootImp主要做了一下六件事情。

  • 1 开始执行dom操作之前,将所有effects执行完毕。
  • 2 before-mutation之前的阶段,全局变量重置,调度useEffect
function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<mixed>,
  renderPriorityLevel: EventPriority
) 
  // --------before-mutation-之前的阶段-start-------
  const finishedWork = root.finishedWork; // rootFiber
  const lanes = root.finishedLanes; //优先级
   // 重置FiberRoot的属性
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
   // 重置变量
  root.callbackNode = null;
  root.callbackPriority = NoLane;
   //开始调度useEffect
       if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) 
    if (!rootDoesHavePassiveEffects) 
      // 赋值全局变量,表示有useEffect的副作用
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
       // 以普通优先级调度useEffect
      scheduleCallback(NormalSchedulerPriority, () =>  
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      );
    
  
   
   ......

  
  • 3 before-mutation阶段,调用commitBeforeMutationEffects
  • 4 mutation阶段,调用commitMutationEffects
  • 5 layout阶段, 调用commitLayoutEffects
function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<mixed>,
  renderPriorityLevel: EventPriority
) 
  // --------before-mutation-之前的阶段-start-------
 
   ......
   
   // 判断是否有effects影响需要更新
  const subtreeHasEffects =  ....// 子树是否有更新
  const rootHasEffect = .... // root是否有更新
  
   if (subtreeHasEffects || rootHasEffect) 
     //因为commit是同步的,优先级也是最高的
     const previousPriority = getCurrentUpdatePriority();
    setCurrentUpdatePriority(DiscreteEventPriority); //设置最高优先级
       
     // -----------------beforeMutation阶段------------------
    const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
      root,
      finishedWork
    );
       
        // ------------mutation阶段------------------
    commitMutationEffects(root, finishedWork, lanes);
   
       // 切换RootFiber.current
    root.current = finishedWork;
       
        //  ------------layout阶段----------------
    commitLayoutEffects(finishedWork, root, lanes);
    if (__DEV__) 
      if (enableDebugTracing) 
        logLayoutEffectsStopped();
      
    
  
  • 6 layout之后阶段,如果有useEffect的effects,就赋值给全局变量rootWithPendingPassiveEffects,useEffect的调度函数通过上面去获取effectLists,执行对应的useEffects函数。调用ensureRootIsScheduled最后判断还有没有更新没执行
function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<mixed>,
  renderPriorityLevel: EventPriority
) 
  // --------before-mutation-之前的阶段-start-------
 
   ......
   
   // 判断是否有effects影响需要更新
  const subtreeHasEffects =  ....// 子树是否有更新
  const rootHasEffect = .... // root是否有更新
  
   if (subtreeHasEffects || rootHasEffect) 
     //因为commit是同步的,优先级也是最高的
     const previousPriority = getCurrentUpdatePriority();
    setCurrentUpdatePriority(DiscreteEventPriority); //设置最高优先级
     // -----------------beforeMutation阶段------------------
        // ------------mutation阶段------------------
        //  ------------layout阶段----------------
      ....
      
// -------------------layout之后--start-------------------
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
       
    if (rootDoesHavePassiveEffects)  // 有useEffect的effects
    // This commit has passive effects. Stash a reference to them. But don't
    // schedule a callback until after flushing layout work.
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root; //将root赋值给rootWithPendingPassiveEffects,useEffect执行的时候会通过他来获取effectList
    pendingPassiveEffectsLanes = lanes;
        
   
       ...
      ensureRootIsScheduled(root, now()); //确保额外的工作正在调度
         // If layout work was scheduled, flush it now.
  flushSyncCallbacks();
       ...
  

commit阶段完成。

接着看对应的每个阶段做的事情。

before-mutation之前

  //当rootWithPendingPassiveEffects不为空的时候,表示有effect 执行effectList上的副作用,直到effectLists上值为null
  do 
    flushPassiveEffects();
   while (rootWithPendingPassiveEffects !== null); //mount的时候为null,

const finishedWork = root.finishedWork; // rootFiber
  const lanes = root.finishedLanes; //优先级
  // 重置FiberRoot的属性
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
 // 重置变量
  root.callbackNode = null;
  root.callbackPriority = NoLane;

 if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) 
    if (!rootDoesHavePassiveEffects) 
      // 赋值全局变量,表示有useEffect的副作用
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      scheduleCallback(NormalSchedulerPriority, () =>  // 以普通优先级调度useEffect
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      );
    
  

可以看到,主要就是三件事情

  • 如果rootWithPendingPassiveEffects有值,就调度flushPassiveEffects,调度useEffect
  • 重置全局变量
  • 如果有useEffect的相关effects,就调用scheduleCallback,以普通优先级调度flushPassiveEffects

flushPassiveEffects最终会调用

export function flushPassiveEffects(): boolean 
    if (rootWithPendingPassiveEffects !== null) 
     // 调用UseEffect的销毁函数
  commitPassiveUnmountEffects(root.current);
  // 调用useEffect函数
  commitPassiveMountEffects(root, root.current);

 



调度useEffect的销毁和执行函数。

rootWithPendingPassiveEffects是在layout阶段之后被赋值的。

before-mutation阶段

  • 根据effectList链表获取有副作用的fiber
  • 类组件执行instance.getSnapShotBeforeUpdate,将返回值挂载到instance上面。
export function commitBeforeMutationEffects(
  root: FiberRoot,
  firstChild: Fiber // workInprogress rootFiber
) 
	...
  nextEffect = firstChild;
  commitBeforeMutationEffects_begin();
	..
  return shouldFire;

firstCHild就是rootFiber,对于commit阶段,他就是第一个要处理的子节点。

调用commitBeforeMutationEffects_begin

function commitBeforeMutationEffects_begin() 
  // 开启while循环
  while (nextEffect !== null) 
    const fiber = nextEffect;
      
  const child = fiber.child;
    if (
      // mount的时候只有rootFiber身材有flags,所以第一次mount,nextEffect最后会被赋值到App
      (fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
      child !== null
    ) 
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
     else 
      commitBeforeMutationEffects_complete();
    
  

开启while循环,遍历effectLists链表,获取到最后一个有副作用的子节点。

mount的时候需要注意,因为只有rootFiber会有flags标记,所以第一次循环,会走if条件,将App fiber赋值给nextEffect。

第二次循环因为App fiber没有flags标记,所以走else条件,commitBeforeMutationEffects_complete

function commitBeforeMutationEffects_complete() 
     while (nextEffect !== null) 
    // 开启while循环
    const fiber = nextEffect;
    commitBeforeMutationEffectsOnFiber(fiber);
   
    const sibling = fiber.sibling;
    if (sibling !== null) 
      ensureCorrectReturnPointer(sibling, fiber.return);
      nextEffect = sibling;
      return;
    

    // 下一个while循环条件
    nextEffect = fiber.return;
  

commitBeforeMutationEffects_complete也开启一个while循环,从下往上处理每一个有flags的fiber。调用commitBeforeMutationEffectsOnFiber

function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) 
  const current = finishedWork.alternate; //获取rooFiber的alternate也就是current rootFiber
  const flags = finishedWork.flags; //获取rootFiber的effectTag
  if ((flags & Snapshot) !== NoFlags) 
      //有flags标记的fiber才会执行
        switch (finishedWork.tag) 
                ..
                case ClassComponent: 
                 if (current !== null) 
                  const prevProps = current.memoizedProps; //旧的props
                  const prevState = current.memoizedState; //旧的state
                  const instance = finishedWork.stateNode; //获取类组件的实例
                  // 调用类组件的getSnapshotBeforeUpdate函数
                  const snapshot = instance.getSnapshotBeforeUpdate(
                    finishedWork.elementType === finishedWork.type
                      ? prevProps
                      : resolveDefaultProps(finishedWork.type, prevProps),
                    prevState
                  );
                  // 将返回的快照值保存在实例的__reactInternalSnapshotBeforeUpdate上,到时候赋值给componentDidUpdate
                  instance.__reactInternalSnapshotBeforeUpdate = snapshot;
                
                break;
              
                
        
      
  


这里主要处理了类组件,执行了getSnapshotBeforeUpdate生命周期,将返回值挂载到

instance.__reactInternalSnapshotBeforeUpdate上。

mutation阶段

主要调用了commitMutationEffects函数,mutation阶段是操作dom的阶段。

commitMutationEffects(root, finishedWork, lanes);

function commitMutationEffects_begin(root: FiberRoot, lanes: Lanes)
     while (nextEffect !== null) 
    const fiber = nextEffect;

    // 删除操作
    const deletions = fiber.deletions;
    if (deletions !== null) 
      for (let i = 0; i < deletions.length; i++) 
          commitDeletion(root, childToDelete, fiber); //对于删除的fiber
      
    

    const child = fiber.child;
    // 跟before-mutation阶段一样的判断条件
    if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) 
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
     else 
      commitMutationEffects_complete(root, lanes);
    
  

commitMutationEffects_begin函数的while循环,跟before-mutation阶段的一样判断条件。这里多了一个对于删除节点的操作。

对于删除的节点,存在fiber.deletion上。调用commitDeletion进行删除。

commitDeletion调用commitNestedUnmounts函数。

主要执行对dom的删除。

function commitNestedUnmounts(
  finishedRoot: FiberRoot,
  root: Fiber,
  nearestMountedAncestor: Fiber
)
      // 递归删除所有子节点,将子节点都调用一次commitUnmount函数。
      while (true) 
    commitUnmount(finishedRoot, node, nearestMountedAncestor);
    if (
      node.child !== null &&
      (!supportsMutation || node.tag !== HostPortal)
    ) 
      node.child.return = node;
      node = node.child;
      continue;
    
    if (node === root) 
      return;
    
    while (node.sibling === null) 
      if (node.return === null || node.return === root) 
        return;
      
      node = node.return;
    
    node.sibling.return = node.return;
    node = node.sibling;
  
  
function commitUnmount(
  finishedRoot: FiberRoot,
  current: Fiber,
  nearestMountedAncestor: Fiber
)
       switch (current.tag) 
                 case FunctionComponent:
                      const updateQueue: FunctionComponentUpdateQueue | null =
        (current.updateQueue: any);
      if (updateQueue !== null) 
        const lastEffect = updateQueue.lastEffect;
        if (lastEffect !== null) 
          const firstEffect = lastEffect.next;

          let effect = firstEffect;
          do 
            // 调用useLyaoutEffect的销毁函数
            const  destroy, tag  = effect;
            if (destroy !== undefined) 
                ....
               safelyCallDestroy(current, nearestMountedAncestor, destroy);
                ...
                
              
            
            effect = effect.next;
           while (effect !== firstEffect);
        
      
      return;
                 
      .....
      // 对于类组件
       case ClassComponent: 
      safelyDetachRef(current, nearestMountedAncestor);
      const instance = current.stateNode;
      if (typeof instance.componentWillUnmount === "function") 
        safelyCallComponentWillUnmount(
          current,
          nearestMountedAncestor,
          instance
        );
      
      return;
    
      ....
       
....
      
  
  • useLayoutEffect是以链表的形式存放在fiber.updateQueue.lastEffect之上。这里对于函数组件的操作就是获取所有effects,调用其销毁函数destroy。记住,useLayoutEffect销毁函数是在mutation阶段执行的。
  • 对于类组件,处理ref,调用类组件的componentWillUnMount函数。
commitMutationEffects_complete

跟before-mutation阶段一样

function commitMutationEffects_complete(root: FiberRoot, lanes: Lanes) 
  while (nextEffect !== null) 
    // 开启while循环
    const fiber = nextEffect;
    ...
      commitMutationEffectsOnFiber(fiber, root, lanes);
   ...
    const sibling = fiber.sibling;
    if (sibling !== null) 
      ensureCorrectReturnPointer(sibling, fiber.return);
      nextEffect = sibling;
      return;
    

    nextEffect = fiber.return;
  

开启while循环,执行所有的有flags的fiber,调用commitMutationEffectsOnFiber函数

// mutation阶段主要执行的函数
function commitMutationEffectsOnFiber(
  finishedWork: Fiber, // fiber
  root: FiberRoot, //FiberROot
  lanes: Lanes
) 
      const flags = finishedWork.flags; //effectTag
      
       // 有ref的effectTag
      if (flags & Ref) 
        const current = finishedWork.alternate;
        if (current !== null) 
          commitDetachRef(current); //清除ref
        
      
      
        // 判断当前的fiber要做啥操作
  const primaryFlags = flags & (Placement | Update | Hydrating);
      switch (primaryFlags) 
              case Placement: 
                  // 新增
                  commitPlacement(finishedWork);
                  finishedWork.flags &= ~Placement;
                  break;
                
               case PlacementAndUpdate: 
                      // 新增并且修改
                      commitPlacement(finishedWork);
                      finishedWork.flags &= ~Placement;
                      // Update
                      const current = finishedWork.alternate;
                      commitWork(current, finishedWork);
                      break;
   				 
             	...
                case Update: 
                  // 修改
                  const current = finishedWork.alternate;
                  commitWork(current, finishedWork);
                  break;
    
      
  

commitMutationEffectsOnFiber主要处理了包含有ref的flags的fiber,先清除老的ref,等到layout阶段再赋值新的ref

function commitDetachRef(current: Fiber) 
  const currentRef = current.ref;
  if (currentRef !== null) 
    if (typeof currentRef === "function") 
      if (
        enableProfilerTimer &&
        enableProfilerCommitHooks &&
        current<

以上是关于react源码debugger-commit阶段的完成的主要内容,如果未能解决你的问题,请参考以下文章

react源码解析8.render阶段

react源码总结

react源码总结

react源码总结

react源码总结

React源码Part6——Commit阶段(beforeMutation)