React解密:React中setState的运行原理是什么

我们知道react中更新state的时候,会调用setState方法。那么调用setState方法为什么就能更新组件的state呢,其原理又是什么呢?下面我们就来讲讲setState的原理:

setState() 将对组件 state 的更改排入队列批量推迟更新,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。其实setState实际上不是异步,只是代码执行顺序不同,有了异步的感觉。

setState(stateChange | updater [, callback])
  • stateChange - 作为被传入的对象,将被浅层合并到新的 state 中
  • updater - (state, props) => stateChange,返回基于 state 和 props 构建的新对象,将被浅层合并到新的 state 中
  • callback - 为可选的回调函数

所以在调用完setState之后立即调用this.state是拿不到state的最新状态的。而且如果多次调用setState,会将多次的结果进行合并。可以使用 componentDidUpdate() 或者 setState(updater, callback) 中的回调函数 callback 保证在应用更新后触发,通常建议使用 componentDidUpdate()。

为了更好的感知性能,React 会在同一周期内会对多个 setState() 进行批处理。通过触发一次组件的更新来引发回流。后调用的 setState() 将覆盖同一周期内先调用 setState() 的值。所以如果是下一个 state 依赖前一个 state 的话,推荐给 setState() 传 function。

下面来看看setState的源码,从源码的角度来理解setState:

(1)setState

//    src/isomorphic/modern/class/ReactBaseClasses.js

ReactComponent.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
}

React组件继承自React.Component,而setState是React.Component的方法,因此对于组件来讲setState属于其原型方法。

(2)enqueueSetState(); enqueueCallback()

//    src/renderers/shared/stack/reconciler/ReactUpdateQueue.js
enqueueSetState: function(publicInstance, partialState) {
    var internalInstance = getInternalInstanceReadyForUpdate(
      publicInstance,
      'setState',
    );
    if (!internalInstance) {
      return;
    }
    var queue =
      internalInstance._pendingStateQueue ||
      (internalInstance._pendingStateQueue = []);
    queue.push(partialState);
    enqueueUpdate(internalInstance);
}
enqueueCallback: function(publicInstance, callback, callerName) {
    ReactUpdateQueue.validateCallback(callback, callerName);
    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
    if (!internalInstance) {
      return null;
    }
    if (internalInstance._pendingCallbacks) {
      internalInstance._pendingCallbacks.push(callback);
    } else {
      internalInstance._pendingCallbacks = [callback];
    }
    enqueueUpdate(internalInstance);
  }

(3)enqueueUpdate()

//    src/renderers/shared/stack/reconciler/ReactUpdates.js
function enqueueUpdate(component) {
  ensureInjected();
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

如果处于批量更新模式,也就是 isBatchingUpdates 为 true 时,不进行state的更新操作,而是将需要更新的 component 添加到 dirtyComponents 数组中。

如果不处于批量更新模式,对所有队列中的更新执行 batchedUpdates 方法。

(4)batchedUpdates()

//    src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,
  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
    ReactDefaultBatchingStrategy.isBatchingUpdates = true;
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

如果 isBatchingUpdates 为 true,当前正处于更新事务状态中,则将 Component 存入 dirtyComponent 中, 否则调用 batchedUpdates 处理,发起一个 transaction.perform()。

(5)transaction initialize and close

//    src/renderers/shared/stack/reconciler/ReactDefaultBatchingStrategy.js
var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function() {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  },
};
var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};

Transaction 中注册了两个 wrapper,RESET_BATCHED_UPDATESFLUSH_BATCHED_UPDATES

initialize 阶段,两个 wrapper 都是空方法,什么都不做。

close 阶段,RESET_BATCHED_UPDATES 将 isBatchingUpdates 设置为false;FLUSH_BATCHED_UPDATES 运行 flushBatchedUpdates 执行update。

(6)更新渲染

ReactUpdates.flushBatchedUpdates() - ReactUpdates.runBatchedUpdates() - ReactCompositeComponent.performUpdateIfNecessary() - receiveComponent() + updateComponent()

  • runBatchedUpdates循环遍历dirtyComponents数组,主要干两件事:

    • 首先执行 performUpdateIfNecessary 来刷新组件的 view
    • 执行之前阻塞的 callback
  • receiveComponent 最后会调用 updateComponent

  • updateComponent 中会执行 React 组件存在期的生命周期方法,如 componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate,render, componentDidUpdate。 从而完成组件更新的整套流程

  • 在shouldComponentUpdate之前,执行了_processPendingState方法,该方法主要对state进行处理:

    • 如果更新队列为null,那么返回原来的state;
    • 如果更新队列有一个更新,那么返回更新值;
    • 如果更新队列有多个更新,那么通过for循环将它们合并;
  • 在一个生命周期内,在componentShouldUpdate执行之前,所有的state变化都会被合并,最后统一处理。

flushBatchedUpdates(); runBatchedUpdates() 

//    src/renderers/shared/stack/reconciler/ReactUpdates.js
var flushBatchedUpdates = function() {
  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);
    }
  }
};
function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;
  dirtyComponents.sort(mountOrderComparator);
  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    // dirtyComponents中取出一个component
    var component = dirtyComponents[i];
    // 取出dirtyComponent中的未执行的callback
    var callbacks = component._pendingCallbacks;
    component._pendingCallbacks = null;

    var markerName;
    if (ReactFeatureFlags.logTopLevelRenders) {
      var namedComponent = component;
      if (component._currentElement.type.isReactTopLevelWrapper) {
        namedComponent = component._renderedComponent;
      }
      markerName = 'React update: ' + namedComponent.getName();
      console.time(markerName);
    }
    // 执行updateComponent
    ReactReconciler.performUpdateIfNecessary(
      component,
      transaction.reconcileTransaction,
      updateBatchNumber,
    );
    // 执行dirtyComponent中之前未执行的callback
    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(
          callbacks[j],
          component.getPublicInstance(),
        );
      }
    }
  }
}

performUpdateIfNecessary()

//    src/renderers/shared/stack/reconciler/ReactReconciler.js
performUpdateIfNecessary: function(
    internalInstance,
    transaction,
    updateBatchNumber,
  ) {
    internalInstance.performUpdateIfNecessary(transaction);
  },
};

performUpdateIfNecessary()

//    src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
  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;
    }
  },

setState的执行流程:

setState 流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件,React大量运用了注入机制,这样每次注入的都是同一个实例化对象,防止多次实例化。

  1. enqueueSetState 将 state 放入队列中,并调用 enqueueUpdate 处理要更新的 Component
  2. 如果组件当前正处于 update 事务中,则先将 Component 存入 dirtyComponent 中。否则调用 batchedUpdates 处理
  3. batchedUpdates 发起一次 transaction.perform() 事务
  4. 开始执行事务初始化,运行,结束三个阶段
    • 初始化:事务初始化阶段没有注册方法,故无方法要执行
    • 运行:执行 setSate 时传入的 callback 方法,一般不会传 callback 参数
    • 结束:执行 RESET_BATCHED_UPDATES FLUSH_BATCHED_UPDATES 这两个 wrapper 中的 close 方法
  5. FLUSH_BATCHED_UPDATES 在 close 阶段,flushBatchedUpdates 方法会循环遍历所有的 dirtyComponents ,调用 updateComponent 刷新组件,并执行它的 pendingCallbacks , 也就是 setState 中设置的 callback

组件挂载后,setState一般是通过DOM交互事件触发,如 click:

  • 点击button按钮
  • ReactEventListener 会触发 dispatchEvent方法
  • dispatchEvent 调用 ReactUpdates.batchedUpdates
  • 进入事务,init 为空, anyMethod 为 ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);
    • handleTopLevelImpl 是在这边调用DOM事件对应的回调方法
    • 然后是setState()
      • 将state的变化和对应的回调函数放置到 _pendingStateQueue ,和 _pendingCallback 中
      • 把需要更新的组件放到 dirtyComponents 序列中
    • 执行 perform()
    • 执行 close 渲染更新
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值