React16源码: React中commit阶段的commitAllLifeCycles的源码实现

commitAllLifeCycles


1 )概述

  • 在 react commit 阶段的 commitRoot 第三个 while 循环中
  • 处理了生命周期相关的一些内容
  • 它这个方法的名字叫做 commitAllLifeCycles

2 )源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js#L479

查看 commitAllLifeCycles

function commitAllLifeCycles(
  finishedRoot: FiberRoot,
  committedExpirationTime: ExpirationTime,
) {
  if (__DEV__) {
    ReactStrictModeWarnings.flushPendingUnsafeLifecycleWarnings();
    ReactStrictModeWarnings.flushLegacyContextWarning();

    if (warnAboutDeprecatedLifecycles) {
      ReactStrictModeWarnings.flushPendingDeprecationWarnings();
    }
  }
  // 通过遍历 firstEffect 到 lastEffect 的单项链表,基于每一个effect对应的fiber对象,对它做出对应的操作
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;
    // 这边的操作设置的是 update 和 callback ,如果有 Update | Callback 就执行 commitLifeCycles
    if (effectTag & (Update | Callback)) {
      recordEffect();
      const current = nextEffect.alternate;
      commitLifeCycles(
        finishedRoot,
        current,
        nextEffect,
        committedExpirationTime,
      );
    }

    if (effectTag & Ref) {
      recordEffect();
      commitAttachRef(nextEffect);
    }

    if (enableHooks && effectTag & Passive) {
      rootWithPendingPassiveEffects = finishedRoot;
    }

    nextEffect = nextEffect.nextEffect;
  }
}
  • 进入 commitLifeCycles

    function commitLifeCycles(
      finishedRoot: FiberRoot,
      current: Fiber | null,
      finishedWork: Fiber,
      committedExpirationTime: ExpirationTime,
    ): void {
      // 根据不同的组件类型来执行不同的操作
      switch (finishedWork.tag) {
        case FunctionComponent:
        case ForwardRef:
        case SimpleMemoComponent: {
          commitHookEffectList(UnmountLayout, MountLayout, finishedWork);
          break;
        }
        // 主要是 ClassComponent,它会根据 current 是否等于 null 来执行不同的生命周期方法
        // 一个是 componentDidMount 跟 componentDidUpdate
        // 因为我们只有在第一次渲染的时候才会调用 componentDidMount 这个生命周期方法
        // 后续调用的都是 componentDidUpdate 它们的调用方式会有一定的区别
        // 这里是 ClassComponent 它的一个commit的过程
        case ClassComponent: {
          const instance = finishedWork.stateNode;
          if (finishedWork.effectTag & Update) {
            // 第一次调用
            if (current === null) {
              startPhaseTimer(finishedWork, 'componentDidMount');
              // 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) {
                  warning(
                    instance.props === finishedWork.memoizedProps,
                    'Expected instance props to match memoized props before ' +
                      'componentDidMount. This is likely due to a bug in React. ' +
                      'Please file an issue.',
                  );
                  warning(
                    instance.state === finishedWork.memoizedState,
                    'Expected instance state to match memoized state before ' +
                      'componentDidMount. This is likely due to a bug in React. ' +
                      'Please file an issue.',
                  );
                }
              }
              instance.componentDidMount();
              stopPhaseTimer();
            } else {
              // 更新
              const prevProps =
                finishedWork.elementType === finishedWork.type
                  ? current.memoizedProps
                  : resolveDefaultProps(finishedWork.type, current.memoizedProps);
              const prevState = current.memoizedState;
              startPhaseTimer(finishedWork, 'componentDidUpdate');
              // 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) {
                  warning(
                    instance.props === finishedWork.memoizedProps,
                    'Expected instance props to match memoized props before ' +
                      'componentDidUpdate. This is likely due to a bug in React. ' +
                      'Please file an issue.',
                  );
                  warning(
                    instance.state === finishedWork.memoizedState,
                    'Expected instance state to match memoized state before ' +
                      'componentDidUpdate. This is likely due to a bug in React. ' +
                      'Please file an issue.',
                  );
                }
              }
              instance.componentDidUpdate(
                prevProps,
                prevState,
                instance.__reactInternalSnapshotBeforeUpdate, // 在 commitRoot 的方法里面去生成的 instance 对象上面的 snapshot 快照
              );
              stopPhaseTimer();
            }
          }
          const updateQueue = finishedWork.updateQueue;
          if (updateQueue !== null) {
            if (__DEV__) {
              if (finishedWork.type === finishedWork.elementType) {
                warning(
                  instance.props === finishedWork.memoizedProps,
                  'Expected instance props to match memoized props before ' +
                    'processing the update queue. This is likely due to a bug in React. ' +
                    'Please file an issue.',
                );
                warning(
                  instance.state === finishedWork.memoizedState,
                  'Expected instance state to match memoized state before ' +
                    'processing the update queue. This is likely due to a bug in React. ' +
                    'Please file an issue.',
                );
              }
            }
            // 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.
            // 对于 ClassComponent,还需要做一件事情,就是去获取它的 updatequeen
            // 然后 执行 commitUpdateQueue 这么一个方法,我们要拿到他的 props
            commitUpdateQueue(
              finishedWork,
              updateQueue,
              instance,
              committedExpirationTime,
            );
          }
          return;
        }
        // 对于 HostRoot,它也会执行 commitUpdateQueue
        // 因为对于 HostRoot 来说,基本上只有在调用 ReactDOM.render 的时候才会创建update
        // 它的 commitUpdateQueue, 能做什么呢?对于调用 ReactDOM.render 是可以传3个参数的
        // 第3个参数就是在第一次 render 结束之后,它的一个回调, 执行过程类似
        case HostRoot: {
          const updateQueue = finishedWork.updateQueue;
          if (updateQueue !== null) {
            let instance = null;
            // 注意这里,有子树的场景
            if (finishedWork.child !== null) {
              switch (finishedWork.child.tag) {
                // 这里
                case HostComponent:
                  instance = getPublicInstance(finishedWork.child.stateNode); // 获取 对应 HostComponent 的 dom 节点,就是返回传参
                  break;
                // 这里,其实和上面一样
                case ClassComponent:
                  instance = finishedWork.child.stateNode;
                  break;
              }
            }
            commitUpdateQueue(
              finishedWork,
              updateQueue,
              instance,
              committedExpirationTime,
            );
          }
          return;
        }
        case HostComponent: {
          const instance: Instance = finishedWork.stateNode;
    
          // Renderers may schedule work to be done after host components are mounted
          // (eg DOM renderer may schedule auto-focus for inputs and form controls).
          // These effects should only be committed when components are first mounted,
          // aka when there is no current/alternate.
          // 参考下面的 commitMount 注释
          if (current === null && finishedWork.effectTag & Update) {
            const type = finishedWork.type;
            const props = finishedWork.memoizedProps;
            commitMount(instance, type, props, finishedWork);
          }
    
          return;
        }
        case HostText: {
          // We have no life-cycles associated with text.
          return;
        }
        case HostPortal: {
          // We have no life-cycles associated with portals.
          return;
        }
        case Profiler: {
          if (enableProfilerTimer) {
            const onRender = finishedWork.memoizedProps.onRender;
    
            if (enableSchedulerTracing) {
              onRender(
                finishedWork.memoizedProps.id,
                current === null ? 'mount' : 'update',
                finishedWork.actualDuration,
                finishedWork.treeBaseDuration,
                finishedWork.actualStartTime,
                getCommitTime(),
                finishedRoot.memoizedInteractions,
              );
            } else {
              onRender(
                finishedWork.memoizedProps.id,
                current === null ? 'mount' : 'update',
                finishedWork.actualDuration,
                finishedWork.treeBaseDuration,
                finishedWork.actualStartTime,
                getCommitTime(),
              );
            }
          }
          return;
        }
        case SuspenseComponent:
          break;
        case IncompleteClassComponent:
          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.',
          );
        }
      }
    }
    
    • 进入 commitUpdateQueue
      // packages/react-reconciler/src/ReactUpdateQueue.js#L571
      export function commitUpdateQueue<State>(
        finishedWork: Fiber,
        finishedQueue: UpdateQueue<State>,
        instance: any,
        renderExpirationTime: ExpirationTime,
      ): void {
        // If the finished render included captured updates, and there are still
        // lower priority updates left over, we need to keep the captured updates
        // in the queue so that they are rebased and not dropped once we process the
        // queue again at the lower priority.
      
        // 这里做了一个判断,是否有 firstCapturedUpdate,在 finishedqueen 上面, 也就是组件的 updateQueen 上面
        // 这个 updateQueue上面, 如果有 capturedupdate,就是说我们在渲染这个组件的子树的时候,如果有异常出现
        // 并且被这个组件捕获,会产生这部分的update, 这部分的update,有可能在这一次的渲染周期里面, 没有被执行完
        // 其实这部分它有一个用意,就是说对于被捕获的错误,我们如果在这一次渲染周期里面无法去处理
        // 没有办法去完成它,我们把它放到这个组件上面,如果它还有低优先级的更新的话
        // 那么我们把它放到低优先级的更新上面去看他是否在那个低优先级的更新上能够被处理
        // 如果我们没有低优先级的处理了, 如果我们没有低优先级的更新了
        // 我们在本次渲染当中捕获的更新,就直接给它清空了
        // 因为我们本次渲染里面没有完成,为了不影响之后产生的更新,我们就直接清空它了
        // 而不需要去让它在后续影响整个组件的一个更新过程
        if (finishedQueue.firstCapturedUpdate !== null) {
          // Join the captured update list to the end of the normal list.
          // 这个时候它会进行一个判断, 如果我们现在这个组件上面还有 update 不是的 capturedUpdate, 而是普通的我们自己创建的update
          if (finishedQueue.lastUpdate !== null) {
            // 就把capturedupdate放到正常的update的后面,就把它在链表上面去给它接在最后面这一部分
            finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;
            finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;
          }
          // Clear the list of captured updates.
          // 不管有没有,我们都要执行,把firstCapturedUpdate到lastCapturedUpdate这个链表上面的数据清空
          finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;
        }
      
        // Commit the effects
        // commitUpdateEffects 它不仅仅要对 firstEffect 来执行
        commitUpdateEffects(finishedQueue.firstEffect, instance);
        finishedQueue.firstEffect = finishedQueue.lastEffect = null;
      
        // 而同样的对于 capturedEffect 也要进行执行
        // 比如之前 workLoop 中看到过的 throw exception 里面
        // 去捕获了这个错误,然后为 classcomponent 创建了一个 update
        // throw exception 里面,在 unwindwork 中的 createClassErrorUpdate, 在里面 放入了update它的callback
        // 就是去调用 componentDidCatch 这个生命周期方法,这个callback 也是会在这个地方被执行的
        commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);
        finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
      }
      
      • 进入 commitUpdateEffects
        function commitUpdateEffects<State>(
          effect: Update<State> | null,
          instance: any,
        ): void {
          while (effect !== null) {
            const callback = effect.callback;
            // 判断一下这个 effect 上面是否有 callback
            if (callback !== null) {
              effect.callback = null;
              // 如果有callback,我们执行callback
              callCallback(callback, instance);
            }
            effect = effect.nextEffect;
          }
        }
        // setState 中的 回调就是在这个时候执行的
        // 在这个时间点上,这个 update 对应的更新已经执行了,它已经反映到了我们的 state 和 props 上面
        // 在 setState 中的 回调才会执行
        // 在之前的 processUpdateQueue 方法里面,没有执行回调的过程的,如果有回调,回调就会把它放到对应的 update 里面
        function callCallback(callback, context) {
          invariant(
            typeof callback === 'function',
            'Invalid argument passed as callback. Expected a function. Instead ' +
              'received: %s',
            callback,
          );
          callback.call(context);
        }
        
    • 进入 commitMount
      // 先判断了一下是否要 autofocus 这个 HostComponent
      // 在之前 completeUnitOfWork 的时候,会判断过这个组件是否有 autofocus 这个属性的
      // 然后会给它加上一个 update 的 SideEffect
      // 而在这里,同样判断它是是否有 update 这个 SideEffect
      // 同时它只有在 current 等于 null 的一个情况下才会被调用 (限制条件在外部调用方)
      // 其实 这也就符合我们对于 autofocus 的一个认知
      // 在页面初次渲染的时候,它才会去自动获取需要被 autofocus 的节点 
      // 同样的对于react当中它的一个判断方式就非常的简单,就是通过current是否等于null来进行判断。
      // 如果有update,我们就执行这个方法来判断一下它是否是一个autofocus的组件。
      // 然后进行一个手动触发这个focus的过程
      export function commitMount(
        domElement: Instance,
        type: string,
        newProps: Props,
        internalInstanceHandle: Object,
      ): void {
        // Despite the naming that might imply otherwise, this method only
        // fires if there is an `Update` effect scheduled during mounting.
        // This happens if `finalizeInitialChildren` returns `true` (which it
        // does to implement the `autoFocus` attribute on the client). But
        // there are also other cases when this might happen (such as patching
        // up text content during hydration mismatch). So we'll check this again.
        if (shouldAutoFocusHostComponent(type, newProps)) {
          ((domElement: any):
            | HTMLButtonElement
            | HTMLInputElement
            | HTMLSelectElement
            | HTMLTextAreaElement).focus();
        }
      }
      
      function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
        switch (type) {
          case 'button':
          case 'input':
          case 'select':
          case 'textarea':
            return !!props.autoFocus;
        }
        return false;
      }
      
  • 以上是对所有的具有 SideEffect 的节点在commit过程当中执行任务的一个过程,到这里,整个dom树其实也已经渲染上去了

  • commitAllLifeCycles 其实主要是调用不同组件的一个生命周期,以及对于 HostComponent,等组件的不同处理

  • 包括可能存在 autofocus 的处理过程,所以它相当于是在整个应用渲染完成之后的一个善后工作

  • 具体细节,写在上述代码的注释中

你可能感兴趣的:(React,React,Native,react.js,前端,前端框架)