Heim > Web-Frontend > js-Tutorial > Detaillierte Erläuterung des setState-Quellcodes in React

Detaillierte Erläuterung des setState-Quellcodes in React

小云云
Freigeben: 2018-01-18 11:41:00
Original
1586 Leute haben es durchsucht

Dieser Artikel stellt hauptsächlich die eingehende Untersuchung des setState-Quellcodes in React vor. Der Herausgeber findet ihn recht gut, daher werde ich ihn jetzt mit Ihnen teilen und als Referenz verwenden. Folgen wir dem Herausgeber und schauen wir uns das an. Ich hoffe, es kann allen helfen.

Als Front-End-Framework konzentriert sich React nur auf den View-Teil von MVVM, realisiert aber dennoch die Bindung von View und Modell. Während Sie die Daten ändern, kann die Ansicht aktualisiert werden. Dies vereinfacht unsere Logik erheblich und muss sich nur um Änderungen im Datenfluss kümmern. Außerdem wird die Codemenge reduziert und die spätere Wartung komfortabler. Diese Funktion ist der Methode setState() zu verdanken. React verwendet einen Warteschlangenmechanismus zur Statusverwaltung und vermeidet so viele wiederholte Aktualisierungen der Ansicht. Lassen Sie uns den setState-Mechanismus aus der Quellcode-Perspektive untersuchen.

1 Lassen Sie uns zuerst eine Komponente deklarieren und die Quelle Schritt für Schritt von Anfang an finden


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');
 }
};
Nach dem Login kopieren

Die Hauptsache ist also, zu sehen, ob die Wie die spezifischen Updater-Parameter übergeben werden und um welches Objekt es sich handelt, erfahren Sie in der Artikelserie „React Source Code Analysis“. wie der Kontext-Updater in React übergeben wird

Lassen Sie uns hier direkt über die Ergebnisse sprechen. Das Updater-Objekt ist tatsächlich das in ReactUpdateQueue.js verfügbar gemachte Objekt

2 Jetzt haben wir die Aktion gefunden Nach setState ausgeführt, gehen wir Schritt für Schritt vor


ReactUpdateQueue.js
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, &#39;setState&#39;);
 }
};
Nach dem Login kopieren


Sie können sehen, dass enqueueSetState enqueueCallback schließlich enqueueUpdate; ReactDefaultBatchingStrategy.js
var ReactUpdates = require(&#39;./ReactUpdates&#39;);

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, &#39;setState&#39;);
  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;
Nach dem Login kopieren


Als nächstes schauen wir uns an, wie der Transaktionsverarbeitungsmechanismus in React funktioniert;
function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
}
Nach dem Login kopieren

Transaction.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。
Nach dem Login kopieren

Weiter Führen Sie die Methode „flushBatchedUpdates“ in ReactUpdates.js

ReactUpdates.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.close
var 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&#39;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;
Nach dem Login kopieren

ReactReconciler aus .js


var _prodInvariant = require(&#39;./reactProdInvariant&#39;);
var invariant = require(&#39;fbjs/lib/invariant&#39;);
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&#39;t throw, we don&#39;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)
};
Nach dem Login kopieren

ReactCompositeComponent.js


Das Update von this.state wird ausgeführt nachdem _processPendingState ausgeführt wurde. Der von setState zweimal erhaltene Anfangswert von this.state.count ist also 0, was das vorherige Phänomen erklärt. Tatsächlich ist dies auch die Lösung von React, um die Zustandsabhängigkeit zu lösen, aber der Zustand wird nicht rechtzeitig aktualisiert. Daher muss jeder bei der Verwendung anhand der tatsächlichen Situation beurteilen, welche Methode zum Übergeben von Parametern verwendet werden soll. Schauen wir uns ein kleines Beispiel an, um ein intuitives Gefühl zu bekommen.
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 = &#39;React update: &#39; + 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());
   }
  }
 }
}
Nach dem Login kopieren


Der setState-Prozess ist immer noch sehr kompliziert und das Design ist auch sehr exquisit, wodurch unnötige Aktualisierungen vermieden werden von Komponenten wiederholt. Sein Hauptprozess ist wie folgt
performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
  if (internalInstance._updateBatchNumber !== updateBatchNumber) {
   // The component&#39;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 !== &#39;production&#39;) {
   if (internalInstance._debugID !== 0) {
    ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
   }
  }
 }
Nach dem Login kopieren

enqueueSetState stellt den Status in die Warteschlange und ruft enqueueUpdate auf, um die zu aktualisierende Komponente zu verarbeiten

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 !== &#39;production&#39;) {
    measureLifeCyclePerf(function () {
     return inst.componentWillReceiveProps(nextProps, nextContext);
    }, this._debugID, &#39;componentWillReceiveProps&#39;);
   } 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 !== &#39;production&#39;) {
     shouldUpdate = measureLifeCyclePerf(function () {
      return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
     }, this._debugID, &#39;shouldComponentUpdate&#39;);
    } 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&#39;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 === &#39;function&#39; ? partial.call(inst, nextState, props, context) : partial);
 }

 return nextState;
},
Nach dem Login kopieren

Wenn die Komponente befindet sich derzeit in der Aktualisierungstransaktion. Die Komponente wird zunächst in dirtyComponent gespeichert. Andernfalls rufen Sie die BatchUpdates-Verarbeitung auf.


BatchedUpdates initiiert eine Transaktion „transaction.perform()“
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
 }
...
Nach dem Login kopieren

    Startet die Transaktionsinitialisierung, führt sie aus und endet in drei Phasen
  1. Initialisierung: In der Transaktionsinitialisierungsphase gibt es keine Registrierungsmethode, daher gibt es keine auszuführende Methode

  2. Ausführen: Die Rückrufmethode, die wann übergeben wird Beim Ausführen von setSate wird der Rückrufparameter

  3. im Allgemeinen nicht übergeben. Ende: isBatchingUpdates auf „false“ aktualisieren und die Close-Methode im Wrapper von FLUSH_BATCHED_UPDATES ausführen

    1. FLUSH_BATCHED_UPDATES in der Schließphase. Es durchläuft alle dirtyComponents, ruft updateComponent auf, um die Komponente zu aktualisieren, und führt seine pendingCallbacks aus, bei denen es sich um den in setState festgelegten Rückruf handelt.

    2. Verwandte Empfehlungen:

    3. Der Unterschied zwischen setState in React und Preact

    React Advanced Component Eintragsbeispiel teilen
  4. Eine umfassende Analyse des Unterschieds zwischen dem JavaScript-Framework Angular und React

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des setState-Quellcodes in React. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage