美文网首页
react-reconsiler模块 - React源码解析(二

react-reconsiler模块 - React源码解析(二

作者: 请叫我Pro大叔 | 来源:发表于2020-03-22 11:43 被阅读0次

    简述

    react-reconsiler模块是React中最核心的模块之一,也是最复杂的模块之一。其中包括React Fiber框架、React Hooks等的实现。
    本文主要讲述其中React中Class组件初始化过程和更新过程

    Class组件初始化过程

    在Class组件初始化过程中,React为其创建一个FiberNode实例,其实也是一个WIP(workInProcess)。
    (1)BeginWork过程
    BeginWork过程是处理WIP的第一阶段。在这个过程中,首先是计算组件的props属性,并执行updateClassComponent函数。

    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,
        renderExpirationTime,
      );
    }
    

    updateClassComponent中,将完成类实例的创建,以及挂载实例。

        // ....
        // 构建类组件实例
        constructClassInstance(workInProgress, Component, nextProps);
        mountClassInstance(
          workInProgress,
          Component,
          nextProps,
          renderExpirationTime,
        );
        shouldUpdate = true;
    

    类实例创建过程中,将给实例的updater属性进行赋值,这个很重要,后续组件调用setState更新状态,实际上是调用的该对象的方法。

    function adoptClassInstance(workInProgress: Fiber, instance: any): void {
      instance.updater = classComponentUpdater;
      workInProgress.stateNode = instance;
      // The instance needs access to the fiber so that it can schedule updates
      setInstance(instance, workInProgress);
      // ...
    }
    

    挂载实例过程中:

    1. 首先计算实例的上下文(context)
    const contextType = ctor.contextType;
      if (typeof contextType === 'object' && contextType !== null) {
        instance.context = readContext(contextType);
      } else if (disableLegacyContext) {
        instance.context = emptyContextObject;
      } else {
        const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
        instance.context = getMaskedContext(workInProgress, unmaskedContext);
      }
    
    1. 然后,计算实例的状态(state)
    processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
    instance.state = workInProgress.memoizedState;
    
    1. 接着,调用类组件的getDerivedStateFromProps方法,并重新给类实例的state赋值,
    const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
      if (typeof getDerivedStateFromProps === 'function') {
        applyDerivedStateFromProps(
          workInProgress,
          ctor,
          getDerivedStateFromProps,
          newProps,
        );
        instance.state = workInProgress.memoizedState;
      }
    
    1. 再然后,调用类实例的componentWillMount函数(或UNSAFE_componentWillMount),并重新计算state(因为有可能在改函数中调用了setState,虽然不建议这么做)。
    if (
        typeof ctor.getDerivedStateFromProps !== 'function' &&
        typeof instance.getSnapshotBeforeUpdate !== 'function' &&
        (typeof instance.UNSAFE_componentWillMount === 'function' ||
          typeof instance.componentWillMount === 'function')
      ) {
        callComponentWillMount(workInProgress, instance);
        // If we had additional state updates during this life-cycle, let's
        // process them now.
        processUpdateQueue(
          workInProgress,
          newProps,
          instance,
          renderExpirationTime,
        );
        instance.state = workInProgress.memoizedState;
      }
    
    1. 最后标记类实例是否有componentDidMount函数。
    if (typeof instance.componentDidMount === 'function') {
        workInProgress.effectTag |= Update;
      }
    

    挂载类实例后,React调用类组件的render方法生成组件的子节点,并为子节点生成下一个WIP。

    nextChildren = instance.render();
    // ...
    reconcileChildren(
          current,
          workInProgress,
          nextChildren,
          renderExpirationTime,
        );
    

    (2)completeWork过程
    在完成所有子组件的WIP后,类组件将进入completeWork过程。

    if (next === null) {
        // If this doesn't spawn new work, complete the current work.
        next = completeUnitOfWork(unitOfWork);
    }
    

    类组件的这个阶段比较简单,仅仅只需要处理上下文。

    case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            popLegacyContext(workInProgress);
          }
          return null;
        }
    

    (3)commitWork过程
    在完成所有WIP后,React调用finishSyncRender进入commitWork过程,在这个过程中将调用类实例的componentDidMount函数。

    function commitLifeCycles(
      finishedRoot: FiberRoot,
      current: Fiber | null,
      finishedWork: Fiber,
      committedExpirationTime: ExpirationTime,
    ): void {
      switch (finishedWork.tag) {
        // ...
        case ClassComponent: {
          const instance = finishedWork.stateNode;
          if (finishedWork.effectTag & Update) {
            if (current === null) {
             // ...
              // 更新过程调用componentDidMount
              instance.componentDidMount();
              stopPhaseTimer();
            } else {
              // 更新过程调用componentDidUpdate
              // ...
            }
          }
          const updateQueue = finishedWork.updateQueue;
          if (updateQueue !== null) {
            commitUpdateQueue(finishedWork, updateQueue, instance);
          }
          return;
        }
    

    Class组件更新过程

    Class组件更新一般有以下几种原因:

    • 父组件更新
    • 调用Class组件的setState方法更新
      Class组件的setState方法中更新组件的状态其实是调用其updater属性的enqueueSetState(前面有提到过设置updater属性)。
    Component.prototype.setState = function(partialState, callback) {
      invariant(
        typeof partialState === 'object' ||
          typeof partialState === 'function' ||
          partialState == null,
        'setState(...): takes an object of state variables to update or a ' +
          'function which returns an object of state variables.',
      );
      this.updater.enqueueSetState(this, partialState, callback, 'setState');
    };
    
    • 调用Class组件的forceUpdate方法进行更新
      Class组件的forceUpdate方法中更新组件的状态调用的是其updater属性的enqueueForceUpdate
      enqueueSetState方法和enqueueForceUpdate方法中的逻辑和React初始化的逻辑类似,都会创建一个Update实例,然后调用enqueueUpdate函数以及scheduleWork函数进行更新。
    enqueueSetState(inst, payload, callback) {
        const fiber = getInstance(inst);
        const currentTime = requestCurrentTimeForUpdate();
        const suspenseConfig = requestCurrentSuspenseConfig();
        const expirationTime = computeExpirationForFiber(
          currentTime,
          fiber,
          suspenseConfig,
        );
    
        const update = createUpdate(expirationTime, suspenseConfig);
        update.payload = payload;
        if (callback !== undefined && callback !== null) {
          if (__DEV__) {
            warnOnInvalidCallback(callback, 'setState');
          }
          update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleWork(fiber, expirationTime);
      },
     enqueueForceUpdate(inst, callback) {
        const fiber = getInstance(inst);
        const currentTime = requestCurrentTimeForUpdate();
        const suspenseConfig = requestCurrentSuspenseConfig();
        const expirationTime = computeExpirationForFiber(
          currentTime,
          fiber,
          suspenseConfig,
        );
    
        const update = createUpdate(expirationTime, suspenseConfig);
        update.tag = ForceUpdate;
    
        if (callback !== undefined && callback !== null) {
          if (__DEV__) {
            warnOnInvalidCallback(callback, 'forceUpdate');
          }
          update.callback = callback;
        }
    
        enqueueUpdate(fiber, update);
        scheduleWork(fiber, expirationTime);
      },
    

    注:如何判断setState同步和异步更新state,一般可以根据触发调用setState函数进行更新的是React的内部机制还是外部原因(如,setTimeout的回调函数中调用setState、网络请求响应的处理函数中调用setState)。在React的内部机制中调用setState是异步更新;由外部原因导致的一般是同步更新。
    这里的React的内部机制包括一下几种情况:

    • React事件处理过程中触发,例如:
    <button onClick={this.onClick}>点我点我</button>
    

    onClick中调用setState。这是由于此处的click事件被React代为处理了。

    • 在初始化过程中执行的componentWillMount方法、componentDidMount方法等生命周期钩子函数中调用setState
      这是因为这两种情况都设置了executionContext,使得executionContext不会为NoContext
    // React初始化中调用batchedUpdates,设置executionContext
    export function batchedUpdates<A, R>(fn: A => R, a: A): R {
      const prevExecutionContext = executionContext;
      executionContext |= BatchedContext;
      try {
        return fn(a);
      } finally {
        executionContext = prevExecutionContext;
        if (executionContext === NoContext) {
          // Flush the immediate callbacks that were scheduled during this batch
          flushSyncCallbackQueue();
        }
      }
    }
    // React事件处理过程调用batchedEventUpdates,设置executionContext
    export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
      const prevExecutionContext = executionContext;
      executionContext |= EventContext;
      try {
        return fn(a);
      } finally {
        executionContext = prevExecutionContext;
        if (executionContext === NoContext) {
          // Flush the immediate callbacks that were scheduled during this batch
          flushSyncCallbackQueue();
        }
      }
    }
    

    Class组件更新过程和初始化过程大体一致,同样可以分BeginWorkCompleteWorkCommitWork三个阶段。
    BeginWork阶段
    由于在初始化的时候已经创建了组件实例,这个阶段调用updateClassInstance更新类实例:

    • 计算props
    const oldProps = workInProgress.memoizedProps;
      instance.props =
        workInProgress.type === workInProgress.elementType
          ? oldProps
          : resolveDefaultProps(workInProgress.type, oldProps);
    
    • 计算context
    const oldContext = instance.context;
      const contextType = ctor.contextType;
      let nextContext = emptyContextObject;
      if (typeof contextType === 'object' && contextType !== null) {
        nextContext = readContext(contextType);
      } else if (!disableLegacyContext) {
        const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
        nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
      }
    
    • 调用componentWillReceiveProps
    if (
        !hasNewLifecycles &&
        (typeof instance.UNSAFE_componentWillReceiveProps === 'function' ||
          typeof instance.componentWillReceiveProps === 'function')
      ) {
        if (oldProps !== newProps || oldContext !== nextContext) {
          callComponentWillReceiveProps(
            workInProgress,
            instance,
            newProps,
            nextContext,
          );
        }
      }
    
    • 计算state
    const oldState = workInProgress.memoizedState;
      let newState = (instance.state = oldState);
      processUpdateQueue(workInProgress, newProps, instance, renderExpirationTime);
      newState = workInProgress.memoizedState;
    
    • 设置componentDidUpdate函数和getSnapshotBeforeUpdate函数的标志位,在CompleteWorkCommitWork过程用到。

    CompleteWork阶段
    与初始化过程一样。

    CommitWork阶段
    在这个过程中将根据前面设置的标志位调用componentDidUpdategetSnapshotBeforeUpdate

    function commitLifeCycles(
      finishedRoot: FiberRoot,
      current: Fiber | null,
      finishedWork: Fiber,
      committedExpirationTime: ExpirationTime,
    ): void {
      switch (finishedWork.tag) {
        // ...
        case ClassComponent: {
          const instance = finishedWork.stateNode;
          if (finishedWork.effectTag & Update) {
            if (current === null) {
              // ...
              // 初始化过程调用`componentDidMount`。
            } 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.
              // ...
              // 调用componentDidUpdate
              instance.componentDidUpdate(
                prevProps,
                prevState,
                instance.__reactInternalSnapshotBeforeUpdate,
              );
              stopPhaseTimer();
            }
          }
          const updateQueue = finishedWork.updateQueue;
          if (updateQueue !== null) {
            // ...
            // 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.
            commitUpdateQueue(finishedWork, updateQueue, instance);
          }
          return;
        }
        // ...
      }
    }
    
    function commitBeforeMutationLifeCycles(
      current: Fiber | null,
      finishedWork: Fiber,
    ): void {
      switch (finishedWork.tag) {
        // ...
        case ClassComponent: {
          if (finishedWork.effectTag & Snapshot) {
            if (current !== null) {
              // ...
              // 调用getSnapshotBeforeUpdate
              const snapshot = instance.getSnapshotBeforeUpdate(
                finishedWork.elementType === finishedWork.type
                  ? prevProps
                  : resolveDefaultProps(finishedWork.type, prevProps),
                prevState,
              );
            
          return;
        }
        // ...
      }
     
    }
    

    相关文章

      网友评论

          本文标题:react-reconsiler模块 - React源码解析(二

          本文链接:https://www.haomeiwen.com/subject/miemyhtx.html