React学习之(二)setState的更新机制

继续上文。。。flushBatchedUpdates 的源码

var flushBatchedUpdates = function () {
  // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents
  // array and perform any updates enqueued by mount-ready handlers (i.e.,
  // componentDidUpdate) but we need to check here too in order to catch
  // updates enqueued by setState callbacks and asap calls.
  while (dirtyComponents.length || asapEnqueued) {
    if (dirtyComponents.length) {
      var transaction = ReactUpdatesFlushTransaction.getPooled();
      transaction.perform(runBatchedUpdates, null, transaction);
      ReactUpdatesFlushTransaction.release(transaction);
    }
    if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
    }
  }
};

      在此又出现了一个新的事物ReactUpdatesFlushTransaction,它主要用来捕获在运行flushBatchedUpdate后将要运行的updates。这个过程比较复杂,因为componentDidUpdate或则setState后的回调方法需要进入下一个更新队列。另外这个事物是getpooled来的,而不是实时创建的,这样做的好处是避免不必要的垃圾收集。另外这个地方也涉及到asp update的内容,后续将介绍到。
      在上述源码中,需要在ReactUpdatesFlushTransaction事物的输入参数中调用了runBatchedUpdates,我们来看一看这个方法的逻辑功能:

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
//父组件通常比子组件有更高的reconcile等级,所以需要对dirtyComponents进行排序
  dirtyComponents.sort(mountOrderComparator);
   // reconciling时任何更新队列在整个批量处理后必须执行。然而,假设dirtyComponents是[A,B],B和C是A的child如果C的渲染器为B进行入队列更新,那么在一次批量处理中B需要更新两次。由于B已经更新了,我们将会忽略它,再次更新它的方式只能通过检查bact的数量
  updateBatchNumber++;
  for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];
   //如果,在任何新的更新队列中发生了performUpdateIfNecessary,那么只能在下一次发生渲染时执行回调所以,先将回调存储下来。(后面来看一看什么是performUpdateIfNecessary)
    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.type.isReactTopLevelWrapper) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }
    ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
    if (markerName) {
      console.timeEnd(markerName);
    }
  // 将回调函数放入transaction的回调队列中
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
      }
    }
  }
}

      这个方法遍历所有的dirtyComponent,并根据渲染的顺序对它们进行排序,比如从父节点到子节点的开始更新。运行来自ReactReconciler对象下的performUpdateIfNecessary方法,并将回调函数放入transaction队列中。(修正:runBatchedUpdates的逻辑功能是先排序保证先渲染父组件再渲染子组件,再执行必要的更新,最后将回调函数写入callbackQueue。
现在到最后一步了ReactReconciler
2-3 ReactReconciler&performUpdateIfNecessary
      因为,internalInstance是ReactCompositeComponentWrapper实例,它的prototype继承了ReactCompositeComponent.Mixin,所以可以在ReactCompositeComponent下找到performUpdateIfNecessary这个方法,来看一看它的源码:

performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
    // 组件的批量处理个数应该和当前或者接下来的批量处理个数相同
    if (internalInstance._updateBatchNumber !== updateBatchNumber) {
      return;
    }
    …
    internalInstance.performUpdateIfNecessary(transaction);
   …
  }
};

ReactCompositeComponent文件下找到performUpdateIfNecessary 方法:

  /**
   * If any of `_pendingElement`, `_pendingStateQueue`, or `_pendingForceUpdate`
   * is set, update the component.
   *
   * @param {ReactReconcileTransaction} transaction
   * @internal
   */
  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;
    }
  }

这个方法被分成了两个部分:
      (1) ReactReconciler.receiveComponent是用来在element级别中比较Component,所以element实例被比较后,如果component和element实例不同或者内容发生了变化,那么将会调用内部实例的receiveComponent方法。如果组件相同时,会调用updateComponent方法,这个方法包含了检测逻辑功能。
      (2) updateComponent 如果存在pending state,将会调用这个方法
你可能会思考,为什么需要检测pending状态或者强迫更新,由于调用了setState,所以state处于pending状态?其实不是这样的。真正的原因是updateComponent是递归地更新的组件,但pending state是空的。而且,对_pendingElement的检查是用于处理children被更新的场景。下面来看一看updateComponent的具体实现方法:

updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
    var inst = this._instance;
    !(inst != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Attempted to update component `%s` that has already been unmounted (or failed to mount).', this.getName() || 'ReactCompositeComponent') : _prodInvariant('136', this.getName() || 'ReactCompositeComponent') : void 0;

    var willReceive = false;
    var nextContext;

    // Determine if the context has changed or not
    if (this._context === nextUnmaskedContext) {
      nextContext = inst.context;
    } else {
      nextContext = this._processContext(nextUnmaskedContext);
      willReceive = true;
    }

    var prevProps = prevParentElement.props;
    var 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) {
      if (process.env.NODE_ENV !== 'production') {
        measureLifeCyclePerf(function () {
          return inst.componentWillReceiveProps(nextProps, nextContext);
        }, this._debugID, 'componentWillReceiveProps');
      } else {
        inst.componentWillReceiveProps(nextProps, nextContext);
      }
    }

    var nextState = this._processPendingState(nextProps, nextContext);
    var shouldUpdate = true;

    if (!this._pendingForceUpdate) {
      if (inst.shouldComponentUpdate) {
        if (process.env.NODE_ENV !== 'production') {
          shouldUpdate = measureLifeCyclePerf(function () {
            return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
          }, this._debugID, 'shouldComponentUpdate');
        } else {
          shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
        }
      } else {
        if (this._compositeType === CompositeTypes.PureClass) {
          shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
        }
      }
    }

    if (process.env.NODE_ENV !== 'production') {
      process.env.NODE_ENV !== 'production' ? warning(shouldUpdate !== undefined, '%s.shouldComponentUpdate(): Returned undefined instead of a ' + 'boolean value. Make sure to return true or false.', this.getName() || 'ReactCompositeComponent') : void 0;
    }

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

接下来我们一步步分析这个逻辑方法的步骤:
      (1) 首先,判断内容是否发生了变化,如果内容发生了变化将context,将nextUnmaskedContext经过this._processContext()方法处理后存储到nextContext
      (2) 检测props或者state是否发生改变,如果props发生改变,那么state没有更新,props有更新,则调用componentWillReceiveProps声明周期方法,更新props
      (3) _processPendingState来处理state更新,如果需要更新,则从_pendingStateQueue获取新的replace的标志位以及最新的state。
      (4) 判断component是否需要更新虚拟DOM,如果需要更新则调用_performComponentUpdate,否则只对props、state、context进行更新。
最后来介绍之前说到的ReactUpdates文件中flushBatchedUpdates方法中的asp。

if (asapEnqueued) {
      asapEnqueued = false;
      var queue = asapCallbackQueue;
      asapCallbackQueue = CallbackQueue.getPooled();
      queue.notifyAll();
      CallbackQueue.release(queue);
}
/**
 * Enqueue a callback to be run at the end of the current batching cycle. Throws
 * if no updates are currently being performed.
 */
function asap(callback, context) {
  invariant(batchingStrategy.isBatchingUpdates, "ReactUpdates.asap: Can't enqueue an asap callback in a context where" + 'updates are not being batched.');
  asapCallbackQueue.enqueue(callback, context);
  asapEnqueued = true;
}

      Asap的功能是使当前更新内容结束后调用asapCallbackQueue中的回调函数,如果没更新,则立即调用。
修改:
原理就说到这里了,乱的不得了,下面我们将梳理具体的流程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容