拆解setState[三][一源看世界][之React]

作者: 蛋先生DX | 来源:发表于2016-07-11 11:09 被阅读1062次

    上一章节《拆解setState[二][一源看世界][之React]》讲到了更新过程最核心的方法flushBatchedUpdates,那我们接着聊

    flushBatchedUpdates方法的源码中可以看出,它在ReactUpdatesFlushTransaction这个事务中执行了runBatchedUpdates方法,源码如下:

    function runBatchedUpdates(transaction) {
      var len = transaction.dirtyComponentsLength;
      ...
    
      // Since reconciling a component higher in the owner hierarchy usually (not
      // always -- see shouldComponentUpdate()) will reconcile children, reconcile
      // them before their children by sorting the array.
      dirtyComponents.sort(mountOrderComparator);
    
      // Any updates enqueued while reconciling must be performed after this entire
      // batch. Otherwise, if dirtyComponents is [A, B] where A has children B and
      // C, B could update twice in a single batch if C's render enqueues an update
      // to B (since B would have already updated, we should skip it, and the only
      // way we can know to do so is by checking the batch counter).
      updateBatchNumber++;
    
      for (var i = 0; i < len; i++) {
        // If a component is unmounted before pending changes apply, it will still
        // be here, but we assume that it has cleared its _pendingCallbacks and
        // that performUpdateIfNecessary is a noop.
        var component = dirtyComponents[i];
    
        // If performUpdateIfNecessary happens to enqueue any new updates, we
        // shouldn't execute the callbacks until the next render happens, so
        // stash the callbacks first
        var callbacks = component._pendingCallbacks;
        component._pendingCallbacks = null;
    
        var markerName;
        if (ReactFeatureFlags.logTopLevelRenders) {
          var namedComponent = component;
          // Duck type TopLevelWrapper. This is probably always true.
          if (
            component._currentElement.props ===
            component._renderedComponent._currentElement
          ) {
            namedComponent = component._renderedComponent;
          }
          markerName = 'React update: ' + namedComponent.getName();
          console.time(markerName);
        }
    
        ReactReconciler.performUpdateIfNecessary(
          component,
          transaction.reconcileTransaction,
          updateBatchNumber
        );
    
        if (markerName) {
          console.timeEnd(markerName);
        }
    
        if (callbacks) {
          for (var j = 0; j < callbacks.length; j++) {
            transaction.callbackQueue.enqueue(
              callbacks[j],
              component.getPublicInstance()
            );
          }
        }
      }
    }
    

    这个方法遍历所有的dirty components,通过mount order进行排序(因为更新是从父级到子级),将所有setState的callback方法加入事务的队列,运行ReactReconcilerperformUpdateIfNecessary方法。所以我们得去看看ReactReconciler


    ReactReconciler & performUpdateIfNeeded - 最后的步骤了

    直接看源码实现吧

      performUpdateIfNecessary: function(
        internalInstance,
        transaction,
        updateBatchNumber
      ) {
        ...
        internalInstance.performUpdateIfNecessary(transaction);
        ...
      },
    

    啊,原来是调用了internalInstance的方法,在上一章节中我们说过internalInstanceReactCompositeComponentWrapper实例,它的prototype继承了ReactCompositeComponent.Mixin,所以我们很容易就在ReactCompositeComponent找到了performUpdateIfNecessary这个方法,看下实现吧

      performUpdateIfNecessary: function(transaction) {
        if (this._pendingElement != null) {
          ReactReconciler.receiveComponent(
            this,
            this._pendingElement,
            transaction,
            this._context
          );
        } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
          this.updateComponent(
            transaction,
            this._currentElement,
            this._currentElement,
            this._context,
            this._context
          );
        } else {
          this._updateBatchNumber = null;
        }
      },
    

    这个方法分为两部分:

    • ReactReconciler.receiveComponent - 在element级别去比较components。所以element实例比较后,如果它们不一样或者context改变了,就会触发internal instance的receiveComponent
    • this.updateComponent将被调用,当有pending state的情况

    你可能在想有必要检查pending state或者force updates吗?state必须处于pending状态那是因为你调用了setState,对吗?不是滴,updateComponent是递归的所有你可以有更新的组件,但pending state是空的。同时对_pendingElement的检查是用于处理children被更新的场景。

    updateComponent: function(
        transaction,
        prevParentElement,
        nextParentElement,
        prevUnmaskedContext,
        nextUnmaskedContext
      ) {
        var inst = this._instance;
        ...
    
        var willReceive = false;
        var nextContext;
        var nextProps;
    
        // Determine if the context has changed or not
        if (this._context === nextUnmaskedContext) {
          nextContext = inst.context;
        } else {
          nextContext = this._processContext(nextUnmaskedContext);
          willReceive = true;
        }
    
        nextProps = nextParentElement.props;
    
        // Not a simple state update but a props update
        if (prevParentElement !== nextParentElement) {
          willReceive = true;
        }
    
        // An update here will schedule an update but immediately set
        // _pendingStateQueue which will ensure that any state updates gets
        // immediately reconciled instead of waiting for the next batch.
        if (willReceive && inst.componentWillReceiveProps) {
          ...
          inst.componentWillReceiveProps(nextProps, nextContext);
          ...
        }
    
        var nextState = this._processPendingState(nextProps, nextContext);
        var shouldUpdate = true;
    
        if (!this._pendingForceUpdate && inst.shouldComponentUpdate) {
          ...
          shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
          ...
        }
    
        ...
    
        this._updateBatchNumber = null;
        if (shouldUpdate) {
          this._pendingForceUpdate = false;
          // Will set `this.props`, `this.state` and `this.context`.
          this._performComponentUpdate(
            nextParentElement,
            nextProps,
            nextState,
            nextContext,
            transaction,
            nextUnmaskedContext
          );
        } else {
          // If it's determined that a component should not update, we still want
          // to set props and state but we shortcut the rest of the update.
          this._currentElement = nextParentElement;
          this._context = nextUnmaskedContext;
          inst.props = nextProps;
          inst.state = nextState;
          inst.context = nextContext;
        }
      },
    

    又是一个大方法!我们一步一步来解析它吧:

    • 首先,先对context进行检查。如果改变了,context会被存进nextContext变量中。这里就不进行展开了。
    • updateComponent检查props更新或者只是state更新。如果props更新,则触发componentWillReceiveProps生命周期方法的执行
    • 接下来是处理当前最新的state。_processPendingState方法就是用来搞定这事的
    • 最后是判断component是否应该更新虚拟DOM,如果是,则通过_performComponentUpdate进行更新。如果不是,则仅仅是更新变量的值

    瞧一瞧_processPendingState

      _processPendingState: function(props, context) {
        var inst = this._instance;
        var queue = this._pendingStateQueue;
        var replace = this._pendingReplaceState;
        this._pendingReplaceState = false;
        this._pendingStateQueue = null;
    
        if (!queue) {
          return inst.state;
        }
    
        if (replace && queue.length === 1) {
          return queue[0];
        }
    
        var nextState = assign({}, replace ? queue[0] : inst.state);
        for (var i = replace ? 1 : 0; i < queue.length; i++) {
          var partial = queue[i];
          assign(
            nextState,
            typeof partial === 'function' ?
              partial.call(inst, nextState, props, context) :
              partial
          );
        }
    
        return nextState;
      },
    

    可以看出设置和替换state共享同个队列_pendingStateQueue,有一个属性_pendingReplaceState用于判断是否替换。如果是,pending state将合并replaced state;如果不是则合并当前的state。

    从源码中也可以看出setState的第一个参数可以是个对象,也可以是一个函数,通过这个函数的入参可以拿到实例,当前最新的state, props和context,返回的是一个对象


    ReactUpdates.asap

    这是ReactUpdates的一个重要特性,在asap方法中实现

    function asap(callback, context) {
      ...
      asapCallbackQueue.enqueue(callback, context);
      asapEnqueued = true;
    }
    

    它用在ReactUpdatesflushBatchedUpdates方法上,如:

    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
    

    这个主要用在input elements上。一般情况调用callback的策略如下:所有的更新(包括嵌套的更新)完成后,Callbacks才被调用。Asap使callback可以在当前的更新完成后立即调用 - 所以如果有嵌套的更新,必须等待asap callbacks完成后才能继续


    Wow,设置state真是一个好长好长的流程啊,有没昏昏欲睡的感觉,来个总结吧

    • 调用setState,它把pending state change和callbacks都丢给ReactUpdateQueue处理
    • ReactUpdateQueue更新component的internal instance,把所有更新存放到internal instance的队列变量上,然后交给ReactUpdates
    • ReactUpdates利用batchingStrategy来保证所有state更新在一个事务中执行和刷新
    • flushBatchedUpdates负责同步地原子性地进行更新
    • ReactUpdatesFlushTransaction保证了嵌套的更新被正确地处理
    • runBatchedUpdates的职责是保证更新的顺序,即从父级到子级的顺序进行更新,然后调用ReactReconciler来更新components
    • performUpdateIfNecessary的功能是判断是prop还是state更新,然后调用updateComponent来处理更新
    • updateComponent区分更新的类型,检查shouldComponentUpdate的逻辑以判断是否要阻止虚拟DOM的更新。同时触发了一系列的生命周期方法(shouldComponentUpdatecomponentWillReceivePropscomponentWillUpdatecomponentDidUpdate
    • _processPendingState用于处理pending state并返回当前最新的state对象值。它可以区分是部分设置还是整个替换,并且处理了第一个参数的不同类型入参逻辑(object vs function)
    • 最后介绍了asap callbacks,用于解决输入类型组件更新state的问题 - 它可以让callback在组件更新后立即执行,而不用等到所有嵌套组件完成更新才执行

    最后,期待吐槽,期待指教!!!

    --EOF--

    相关文章

      网友评论

        本文标题:拆解setState[三][一源看世界][之React]

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