setState分析

本文深入分析了React中setState方法的工作原理,包括其异步特性、队列管理、更新策略及如何触发组件重新渲染。通过源码解读,揭示了setState如何在不立即更新状态的情况下,保证UI的一致性和性能优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

setState过程分析

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

大家可以看到我们平时调用的setState方法其实就是调用继承Component中的setState方法,从源码可以看到setState最终是调用this.updater.enqueusSetState方法,那么this.updater在哪里定义的呢,其实是在ReactCopositeComponent文件中组件的mountComponent中定义的,this.updater = updateQueue -> var updateQueue = transaction.getUpdateQueue(),那这个transacion事物又是哪里定义的,实际就是ReactReconcileTransaction中定义的,关于事物transaction我有一节专门讲解的,大家可以看看

  getUpdateQueue: function () {
    return ReactUpdateQueue;
  }
复制代码

可以看到最终是掉用ReactUpdateQueue,那我们从ReactUpdateQueue文件看看enqueueSetState方法的具体实现

enqueueSetState: function (publicInstance, partialState) {

    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');

    if (!internalInstance) {
      return;
    }

    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
    queue.push(partialState);

    enqueueUpdate(internalInstance);
}

function enqueueUpdate(internalInstance) {
  ReactUpdates.enqueueUpdate(internalInstance);
}
复制代码

可以看到我们传入的partialState最终是放在实例中**—pendingStateQueue队列中的,然后又调用ReactUpdates文件中的enqueueUpdate**方法,那我们看看具体实现:

function enqueueUpdate(component) {
  ensureInjected();

  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }

  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}
复制代码

从上边代码可以看出逻辑是这样的:如果没有批量更新任务那就去更新,如果有就会把当前的实例放在dirtyComponents数组中,那我们再看看react的更新策略,在ReactDefaultBatchingStrategy文件中,看一下batchedUpdates源码的实现

  batchedUpdates: function (callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    // The code is written this way to avoid extra allocations
    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  }
复制代码

从上边可以看到,最终是以事物的形式去更行组件,那我们看一下更新策略中事物的源码:

var RESET_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: function () {
    ReactDefaultBatchingStrategy.isBatchingUpdates = false;
  }
};

var FLUSH_BATCHED_UPDATES = {
  initialize: emptyFunction,
  close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
复制代码

这边最终更新组件实际就是调用了ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),那我们看看具体实现

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);
    }
  }
};
复制代码

从上边可以看到实际就是调用了runBatchedUpdates,那我们看看这个方法的实现

function runBatchedUpdates(transaction) {
  var len = transaction.dirtyComponentsLength;

  dirtyComponents.sort(mountOrderComparator);
    
  updateBatchNumber++;

  for (var i = 0; i < len; i++) {
    var component = dirtyComponents[i];
    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);
    }

    if (callbacks) {
      for (var j = 0; j < callbacks.length; j++) {
        transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
      }
    }
  }
}
复制代码

上边代码的大概逻辑就是,遍历dirtyComponents去调用ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber)方法去更新组件,那我们接下来看一下ReactReconciler文件这个方法的实现

  performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
    if (process.env.NODE_ENV !== 'production') {
      if (internalInstance._debugID !== 0) {
        ReactInstrumentation.debugTool.onBeforeUpdateComponent(internalInstance._debugID, internalInstance._currentElement);
      }
    }
    internalInstance.performUpdateIfNecessary(transaction);
    if (process.env.NODE_ENV !== 'production') {
      if (internalInstance._debugID !== 0) {
        ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
      }
    }
  }
复制代码

看到这个我们终于找到了调用组件实例performUpdateIfNecessary更新组件的方法

为什么setState是异步的呢

核心源码


ReactComponent.prototype.setState = function (partialState, callback) {

  this.updater.enqueueSetState(this, partialState);

  if (callback) {

    this.updater.enqueueCallback(this, callback, 'setState');

  }

};



enqueueSetState: function (publicInstance, partialState) {



    var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');



    if (!internalInstance) {

      return;

    }



    var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);

    queue.push(partialState);



    enqueueUpdate(internalInstance);

}





_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;

  }

复制代码

问题一,下面的结果为什么不是4而是2呢?


// 原来num = 1

this.setState({num: 2});

this.setState({num: 4})

复制代码

寻找原因:

  1. state的数据是放在组件实例 _pendingStateQueue 上的,当每次调用setState时实际往这个队列里push

  2. 从上边源码可以看出,实际最终是调用Object.assign进行了合并操作


问题二:为什么回调函数中取到的是修改后的2,而下面取到却是未修改之前的值1呢


    // 原来num = 1

    this.setState({ num :  2 })

    this.setState((nextState, props, context) => {

      console.log(this.state.num)  // 2

    });

   console.log(this.state.num) // 1

复制代码

原因:从源码可以看出,setState是异步的,它首先是放到队列里,最后统一去处理队列,所以下面取不到修改后的值,至于为什么在函数中能取到,这从上边源码很容易就能看出原因,

但是setState中第一个参数是函数在那些场景中能用到了,我暂时还没想到,大家有用到吗?

转载于:https://juejin.im/post/5c1f9c346fb9a049a979b2a8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值