React作為一門前端框架,雖然只是focus在MVVM中的View部分,但還是實現了View和model的綁定。修改數據的同時,可以實現View的刷新。這大大簡化了我們的邏輯,只用關心數據流的變化,同時減少了代碼量,使得后期維護也更加方便。這個特性則要歸功于setState()方法。React中利用隊列機制來管理state,避免了很多重復的View刷新。下面我們來從源碼角度探尋下setState機制。
1 還是先聲明一個組件,從最開始一步步來尋源;
class App extends Component {  //只在組件重新加載的時候執行一次  constructor(props) {    super(props);   //..  }   //other methods}//ReactBaseClasses.js中如下:這里就是setState函數的來源;//super其實就是下面這個函數function ReactComponent(props, context, updater) { this.props = props; this.context = context; this.refs = emptyObject; // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue;}ReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); if (callback) {  this.updater.enqueueCallback(this, callback, 'setState'); }};所以主要來看是否傳入了updater參數,也就是說何時進行 new 組件;具體的updater參數是怎么傳遞進來的,以及是那個對象,參見
react源碼分析系列文章下面的react中context updater到底是如何傳遞的
這里直接說結果,updater對象其實就是ReactUpdateQueue.js 中暴漏出的ReactUpdateQueue對象;
2 既然找到了setState之后執行的動作,我們在一步步深入進去
class Root extends React.Component { constructor(props) {  super(props);  this.state = {   count: 0  }; } componentDidMount() {  let me = this;  me.setState({   count: me.state.count + 1  });  console.log(me.state.count);  // 打印出0  me.setState({   count: me.state.count + 1  });  console.log(me.state.count);  // 打印出0  setTimeout(function(){   me.setState({    count: me.state.count + 1   });   console.log(me.state.count);  // 打印出2  }, 0);  setTimeout(function(){   me.setState({    count: me.state.count + 1   });   console.log(me.state.count);  // 打印出3  }, 0); } render() {  return (   <h1>{this.state.count}</h1>  ) }}ReactComponent.prototype.setState = function (partialState, callback) { this.updater.enqueueSetState(this, partialState); if (callback) {  this.updater.enqueueCallback(this, callback, 'setState'); }};ReactUpdateQueue.js
var ReactUpdates = require('./ReactUpdates');function enqueueUpdate(internalInstance) { ReactUpdates.enqueueUpdate(internalInstance);};function getInternalInstanceReadyForUpdate(publicInstance, callerName) { //在ReactCompositeComponent.js中有這樣一行代碼,這就是其來源; // Store a reference from the instance back to the internal representation  //ReactInstanceMap.set(inst, this); var internalInstance = ReactInstanceMap.get(publicInstance); //返回的是在ReactCompositeComponent.js中construct函數返回的對象;ReactInstance實例對象并不是簡單的new 我們寫的組件的實例對象,而是經過instantiateReactComponent.js中ReactCompositeComponentWrapper函數包裝的對象;詳見 創建React組件方式以及源碼分析.md return internalInstance;};var ReactUpdateQueue = {//。。。。省略其他代碼 enqueueCallback: function (publicInstance, callback, callerName) {  ReactUpdateQueue.validateCallback(callback, callerName);  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);  if (!internalInstance) {   return null;  }//這里將callback放入組件實例的_pendingCallbacks數組中;  if (internalInstance._pendingCallbacks) {   internalInstance._pendingCallbacks.push(callback);  } else {   internalInstance._pendingCallbacks = [callback];  }  // TODO: The callback here is ignored when setState is called from  // componentWillMount. Either fix it or disallow doing so completely in  // favor of getInitialState. Alternatively, we can disallow  // componentWillMount during server-side rendering.  enqueueUpdate(internalInstance); }, enqueueSetState: function (publicInstance, partialState) {  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');  if (!internalInstance) {   return;  }  //這里,初始化queue變量,同時初始化 internalInstance._pendingStateQueue = [ ] ;  //對于 || 的短路運算還是要多梳理下  //queue數組(模擬隊列)中存放著setState放進來的對象;  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);  //這里將partialState放入queue數組中,也就是internalInstance._pendingStateQueue 數組中,此時,每次setState的partialState,都放進了React組件實例對象上的_pendingStateQueue屬性中,成為一個數組;  queue.push(partialState);  enqueueUpdate(internalInstance); },};module.exports = ReactUpdateQueue;可以看到enqueueSetState enqueueCallback 最后都會執行enqueueUpdate;
function enqueueUpdate(internalInstance) { ReactUpdates.enqueueUpdate(internalInstance);}ReactUpdates.js
var dirtyComponents = [];var updateBatchNumber = 0;var asapCallbackQueue = CallbackQueue.getPooled();var asapEnqueued = false;//這里聲明batchingStrategy為null,后期通過注冊給其賦值;var batchingStrategy = null;//這里的component參數是js中ReactCompositeComponentWrapper函數包裝的后的React組件實例對象;function enqueueUpdate(component) { ensureInjected();//第一次執行setState的時候,可以進入if語句,遇到里面的return語句,終止執行 //如果不是正處于創建或更新組件階段,則處理update事務 if (!batchingStrategy.isBatchingUpdates) {  //batchedUpdates就是ReactDefaultBatchingStrategy.js中聲明的  batchingStrategy.batchedUpdates(enqueueUpdate, component);  return; }//第二次執行setState的時候,進入不了if語句,將組件放入dirtyComponents //如果正在創建或更新組件,則暫且先不處理update,只是將組件放在dirtyComponents數組中 dirtyComponents.push(component); if (component._updateBatchNumber == null) {  component._updateBatchNumber = updateBatchNumber + 1; }};//enqueueUpdate包含了React避免重復render的邏輯。mountComponent和updateComponent方法在執行的最開始,會調用到batchedUpdates進行批處理更新,此時會將isBatchingUpdates設置為true,也就是將狀態標記為現在正處于更新階段了。之后React以事務的方式處理組件update,事務處理完后會調用wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES這個wrapper,故最終會調用RESET_BATCHED_UPDATES.close(), 它最終會將isBatchingUpdates設置為false。ReactDefaultBatchingStrategy.js
//RESET_BATCHED_UPDATES用來管理isBatchingUpdates的狀態var RESET_BATCHED_UPDATES = { initialize: emptyFunction, close: function () {  // 事務批更新處理結束時,將isBatchingUpdates設為了false  ReactDefaultBatchingStrategy.isBatchingUpdates = false; }};//FLUSH_BATCHED_UPDATES會在一個transaction的close階段運行runBatchedUpdates,從而執行update。//因為close的執行順序是FLUSH_BATCHED_UPDATES.close ==> 然后RESET_BATCHED_UPDATES.closevar FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)};var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];function ReactDefaultBatchingStrategyTransaction() { this.reinitializeTransaction();}_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, { getTransactionWrappers: function () {  return TRANSACTION_WRAPPERS; }});//這個transition就是下面ReactDefaultBatchingStrategy對象中使用的transaction變量var transaction = new ReactDefaultBatchingStrategyTransaction();var ReactDefaultBatchingStrategy = { isBatchingUpdates: false, /**  * Call the provided function in a context within which calls to `setState`  * and friends are batched such that components aren't updated unnecessarily.  */ batchedUpdates: function (callback, a, b, c, d, e) {  var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;// 批處理最開始時,將isBatchingUpdates設為true,表明正在更新  ReactDefaultBatchingStrategy.isBatchingUpdates = true;  // The code is written this way to avoid extra allocations  if (alreadyBatchingUpdates) {   return callback(a, b, c, d, e);  } else {   //transition在上面已經聲明; // 以事務的方式處理updates,后面詳細分析transaction   return transaction.perform(callback, null, a, b, c, d, e);  } }};module.exports = ReactDefaultBatchingStrategy;接下來我們看下React中的事物處理機制到底是如何運行的;
Transaction.js
var _prodInvariant = require('./reactProdInvariant');var invariant = require('fbjs/lib/invariant');var OBSERVED_ERROR = {};var TransactionImpl = { reinitializeTransaction: function () {  //getTransactionWrappers這個函數ReactDefaultBatchingStrategy.js中聲明的,上面有;返回一個數組;  this.transactionWrappers = this.getTransactionWrappers();  if (this.wrapperInitData) {   this.wrapperInitData.length = 0;  } else {   this.wrapperInitData = [];  }  this._isInTransaction = false; }, _isInTransaction: false, getTransactionWrappers: null, isInTransaction: function () {  return !!this._isInTransaction; }, perform: function (method, scope, a, b, c, d, e, f) {  var errorThrown;  var ret;  try {   this._isInTransaction = true;   errorThrown = true;   //var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];   //1 這里會先執行所有的TRANSACTION_WRAPPERS中成員的initialize方法,上面聲明的其都是emptyFunction   this.initializeAll(0);   //2 這里其實還是執行的 enqueueUpdate 函數   ret = method.call(scope, a, b, c, d, e, f);   errorThrown = false;  } finally {   try {    if (errorThrown) {     // If `method` throws, prefer to show that stack trace over any thrown     // by invoking `closeAll`.     try {      this.closeAll(0);     } catch (err) {}    } else {     // Since `method` didn't throw, we don't want to silence the exception     // here.     //3 執行TRANSACTION_WRAPPERS對象中成員的所有close方法;     this.closeAll(0);    }   } finally {    this._isInTransaction = false;   }  }  return ret; }, initializeAll: function (startIndex) {  var transactionWrappers = this.transactionWrappers;  for (var i = startIndex; i < transactionWrappers.length; i++) {   var wrapper = transactionWrappers[i];   try {        this.wrapperInitData[i] = OBSERVED_ERROR;    this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;   } finally {    if (this.wrapperInitData[i] === OBSERVED_ERROR) {          try {      this.initializeAll(i + 1);     } catch (err) {}    }   }  } }, closeAll: function (startIndex) {  var transactionWrappers = this.transactionWrappers;  for (var i = startIndex; i < transactionWrappers.length; i++) {   var wrapper = transactionWrappers[i];   var initData = this.wrapperInitData[i];   var errorThrown;   try {      errorThrown = true;    if (initData !== OBSERVED_ERROR && wrapper.close) {     wrapper.close.call(this, initData);    }    errorThrown = false;   } finally {    if (errorThrown) {         try {      this.closeAll(i + 1);     } catch (e) {}    }   }  }  this.wrapperInitData.length = 0; }};module.exports = TransactionImpl//3 執行TRANSACTION_WRAPPERS對象中成員的所有close方法;var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)};接著會執行ReactUpdates.js中的flushBatchedUpdates方法
ReactUpdates.js中
var flushBatchedUpdates = function () {  while (dirtyComponents.length || asapEnqueued) {  if (dirtyComponents.length) {   var transaction = ReactUpdatesFlushTransaction.getPooled();   //這里執行runBatchedUpdates函數;   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++) {   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());   }  } }}ReactReconciler.js中
performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {  if (internalInstance._updateBatchNumber !== updateBatchNumber) {   // The component's enqueued batch number should always be the current   // batch or the following one.   return;  } //這里執行React組件實例對象的更新;internalInstance上的performUpdateIfNecessary在ReactCompositeComponent.js中的;  internalInstance.performUpdateIfNecessary(transaction);  if (process.env.NODE_ENV !== 'production') {   if (internalInstance._debugID !== 0) {    ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);   }  } }ReactCompositeComponent.js
performUpdateIfNecessary: function (transaction) { if (this._pendingElement != null) {  // receiveComponent會最終調用到updateComponent,從而刷新View  ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context); } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {  // 執行updateComponent,從而刷新View。  this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context); } else {  this._updateBatchNumber = null; }}, //執行更新React組件的props. state。context函數 updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {  var inst = this._instance;  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);   }  }//這里可以知道為什么setState可以接受函數,主要就是_processPendingState函數;  //這里僅僅是將每次setState放入到_pendingStateQueue隊列中的值,合并到nextState,并沒有真正的更新state的值;真正更新組件的state的值是在下面;  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);    }   }  }  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.   //諾:在這里更新組件的state. props 等值;   this._currentElement = nextParentElement;   this._context = nextUnmaskedContext;   inst.props = nextProps;   inst.state = nextState;   inst.context = nextContext;  } },_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];  //如果是setState的參數是一個函數,那么該函數接受三個參數,分別是state props context  _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial); } return nextState;},this.state的更新會在_processPendingState執行完執行。所以兩次setState取到的都是this.state.count最初的值0,這就解釋了之前的現象。其實,這也是React為了解決這種前后state依賴但是state又沒及時更新的一種方案,因此在使用時大家要根據實際情況來判斷該用哪種方式傳參。來看個小例子直觀感受下
handleClickOnLikeButton () {  this.setState({ count: 0 }) // => this.state.count 還是 undefined  this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN  this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN }//....VS ....handleClickOnLikeButton () {  this.setState((prevState) => {   return { count: 0 }  })  this.setState((prevState) => {   return { count: prevState.count + 1 } // 上一個 setState 的返回是 count 為 0,當前返回 1  })  this.setState((prevState) => {   return { count: prevState.count + 2 } // 上一個 setState 的返回是 count 為 1,當前返回 3  })  // 最后的結果是 this.state.count 為 3 }...setState流程還是很復雜的,設計也很精巧,避免了重復無謂的刷新組件。它的主要流程如下
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持武林網。
新聞熱點
疑難解答