Maison  >  Article  >  interface Web  >  Explication détaillée du code source setState dans React

Explication détaillée du code source setState dans React

小云云
小云云original
2018-01-18 11:41:001507parcourir

Cet article présente principalement l'étude approfondie du code source setState dans React. L'éditeur pense qu'il est plutôt bon, je vais donc le partager avec vous maintenant et le donner comme référence. Suivons l'éditeur pour y jeter un œil, j'espère que cela pourra aider tout le monde.

En tant que framework front-end, React se concentre uniquement sur la partie View de MVVM, mais il réalise toujours la liaison de View et du modèle. Lors de la modification des données, la vue peut être actualisée. Cela simplifie grandement notre logique, nous n'avons qu'à nous soucier des changements dans le flux de données, tout en réduisant la quantité de code, ce qui rend la maintenance ultérieure plus pratique. Cette fonctionnalité est due à la méthode setState(). React utilise un mécanisme de file d'attente pour gérer l'état, évitant ainsi de nombreuses actualisations répétées de View. Explorons le mécanisme setState du point de vue du code source.

1 Déclarons d'abord un composant et trouvons la source étape par étape depuis le début


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');
 }
};

L'essentiel est donc de voir si le Le paramètre de mise à jour est transmis, c'est-à-dire lorsque le nouveau composant est ajouté ; comment les paramètres de mise à jour spécifiques sont transmis et de quel objet il s'agit, veuillez vous référer à la série d'articles d'analyse du code source

react sur comment le programme de mise à jour de contexte est passé dans React

Parlons directement des résultats ici. L'objet de mise à jour est en fait l'objet ReactUpdateQueue exposé dans ReactUpdateQueue.js

2 Maintenant que nous avons trouvé l'action ; effectué après setState, nous allons procéder étape par étape


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

ReactUpdateQueue.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;

Vous pouvez voir que enqueueSetState enqueueCallback finira par exécuter enqueueUpdate; ReactDefaultBatchingStrategy.js


function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
}

Voyons ensuite comment fonctionne le mécanisme de traitement des transactions dans React

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。

Suivant Exécuter la méthode flushBatchedUpdates dans 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;

ReactReconciler .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)
};

ReactCompositeComponent.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 = &#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());
   }
  }
 }
}
La mise à jour de this.state sera exécutée après l'exécution de _processPendingState. Ainsi, la valeur initiale de this.state.count obtenue par setState deux fois est 0, ce qui explique le phénomène précédent. En fait, c'est aussi la solution de React pour résoudre la dépendance de l'état, mais l'état n'est pas mis à jour à temps. Par conséquent, lors de son utilisation, chacun doit juger quelle méthode utiliser pour transmettre les paramètres en fonction de la situation réelle. Regardons un petit exemple pour avoir une sensation intuitive


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);
   }
  }
 }
Le processus setState est encore très compliqué, et la conception est également très exquise, ce qui évite un rafraîchissement inutile. des composants à plusieurs reprises. Son processus principal est le suivant


enqueueSetState met l'état dans la file d'attente et appelle enqueueUpdate pour traiter le composant à mettre à jour

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

Si le composant est actuellement dans Dans la transaction de mise à jour, le composant est d'abord stocké dans dirtyComponent. Sinon, appelez le traitement batchedUpdates.


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
 }
...
batchedUpdates lance une transaction transaction.perform()

  1. Démarre l'initialisation de la transaction, s'exécute et se termine en trois phases

  2. Initialisation : il n'y a pas de méthode d'enregistrement dans la phase d'initialisation de la transaction, il n'y a donc aucune méthode à exécuter.

  3. Exécuter : la méthode de rappel transmise lorsque. l'exécution de setSate n'est généralement pas transmise au paramètre de rappel

  4. Fin : mettez à jour isBatchingUpdates sur false et exécutez la méthode close dans le wrapper de FLUSH_BATCHED_UPDATES

    1. FLUSH_BATCHED_UPDATES dans la phase de fermeture, il parcourra tous les dirtyComponents, appellera updateComponent pour actualiser le composant et exécutera ses endingCallbacks, qui est le rappel défini dans setState.

    2. Recommandations associées :

    3. La différence entre setState dans React et Preact

  5. Composant avancé React partage d'exemple d'entrée

Une grande analyse de la différence entre le framework JavaScript Angular et React


Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn