ReactのsetStateソースコードの詳細説明

小云云
リリース: 2018-01-18 11:41:00
オリジナル
1564 人が閲覧しました

この記事では主に React の setState ソースコードの詳細な研究を紹介します。編集者が非常に優れていると考えたので、参考として共有します。編集者をフォローして見てみましょう。皆さんのお役に立てれば幸いです。

フロントエンド フレームワークとして、React は MVVM の View 部分のみに焦点を当てていますが、それでも View とモデルのバインディングを実現します。データの変更中に、ビューを更新できます。これによりロジックが大幅に簡素化され、データ フローの変更のみを考慮するだけで済み、コードの量も減り、後のメンテナンスがより便利になります。この機能は setState() メソッドのおかげです。 React はキュー メカニズムを使用して状態を管理し、何度も繰り返される View 更新を回避します。ソース コードの観点から setState メカニズムを調べてみましょう。

1 何が渡されるか、そしてそれがどのオブジェクトであるかについては、以下の

react ソースコード分析シリーズの記事を参照して、react のコンテキスト アップデーターがどのように渡されるかを確認してください


ここで結果について直接話しましょう。実際には、ReactUpdateQueue.js で公開されている ReactUpdateQueue オブジェクトです

2 setState の後に実行されるアクションが見つかったので、順を追って説明します

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');
 }
};
ログイン後にコピー

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;);
 }
};
ログイン後にコピー

enqueueSetState enqueueCallback は最終的に enqueueUpdate;


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;
ログイン後にコピー

ReactUpdates.js


function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
}
ログイン後にコピー

ReactDefaultBatchingStrategy.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。
ログイン後にコピー

のトランザクション処理メカニズムを見てみましょう。反応は

トランザクションで動作します。 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;
ログイン後にコピー

その後、ReactUpが実行されますdates.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)
};
ログイン後にコピー

ReactのReactUpdates.jsのflushBatchedUpdatesメソッドCompositeComponent.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());
   }
  }
 }
}
ログイン後にコピー

this.state が更新されます _processPendingState が実行された後に実行します。したがって、setState を 2 回実行して得られる this.state.count の初期値は 0 であり、これで前述の現象が説明されます。実はこれも状態依存性を解決するためのReactの解決策なのですが、状態の更新が間に合わないため、使用する際は実際の状況に応じてどのメソッドを使ってパラメータを渡すかを判断する必要があります。直感的な感覚を得るために、小さな例を見てみましょう

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);
   }
  }
 }
ログイン後にコピー

setState プロセスは依然として非常に複雑ですが、設計も非常に精巧で、不要な更新コンポーネントを繰り返し使用することを避けています。その主なプロセスは次のとおりです

enqueueSetStateは状態をキューに入れ、enqueueUpdateを呼び出して更新対象のコンポーネントを処理します


コンポーネントが現在更新トランザクションにある場合は、まずコンポーネントをdirtyComponentに格納します。それ以外の場合は、batchedUpdates 処理を呼び出します。


batchedUpdatesはtransaction.perform()トランザクションを開始します

  1. トランザクション初期化の開始、実行、終了の3つのフェーズ

  2. 初期化:トランザクション初期化フェーズには登録されたメソッドがないため、メソッドはありません実行する

  3. Run: setSate の実行時に渡されるコールバック メソッドは、通常、コールバック パラメーターを渡しません

  4. End: isBatchingUpdates を false に更新し、FLUSH_BATCHED_UPDATES ラッパーの close メソッドを実行します

    1. FLUSH_BATCHED_UPDATESするだろうクローズフェーズのループ すべてのdirtyComponentsを走査し、updateComponentを呼び出してコンポーネントを更新し、setStateで設定されたコールバックであるpendingCallbacksを実行します。

    2. 関連する推奨事項:

    3. React と Preact の setState の違い

  5. React の高レベルのコンポーネント入力例の共有

JavaScript フレームワーク Angular と React の違いについての大規模な分析


以上がReactのsetStateソースコードの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!