この記事では主に 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'); } };
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'); } };
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;
function enqueueUpdate(internalInstance) { ReactUpdates.enqueueUpdate(internalInstance); }
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。
//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'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;
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) };
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()); } } } }
this.state が更新されます _processPendingState が実行された後に実行します。したがって、setState を 2 回実行して得られる this.state.count の初期値は 0 であり、これで前述の現象が説明されます。実はこれも状態依存性を解決するためのReactの解決策なのですが、状態の更新が間に合わないため、使用する際は実際の状況に応じてどのメソッドを使ってパラメータを渡すかを判断する必要があります。直感的な感覚を得るために、小さな例を見てみましょう
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); } } }
setState プロセスは依然として非常に複雑ですが、設計も非常に精巧で、不要な更新コンポーネントを繰り返し使用することを避けています。その主なプロセスは次のとおりです
コンポーネントが現在更新トランザクションにある場合は、まずコンポーネントをdirtyComponentに格納します。それ以外の場合は、batchedUpdates 処理を呼び出します。
初期化:トランザクション初期化フェーズには登録されたメソッドがないため、メソッドはありません実行する
Run: setSate の実行時に渡されるコールバック メソッドは、通常、コールバック パラメーターを渡しません
End: isBatchingUpdates を false に更新し、FLUSH_BATCHED_UPDATES ラッパーの close メソッドを実行します
FLUSH_BATCHED_UPDATESするだろうクローズフェーズのループ すべてのdirtyComponentsを走査し、updateComponentを呼び出してコンポーネントを更新し、setStateで設定されたコールバックであるpendingCallbacksを実行します。
関連する推奨事項:
React と Preact の setState の違い
以上がReactのsetStateソースコードの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。