React 是一個十分龐大的庫,因爲要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,致使其代碼抽象化程度很高,嵌套層級很是深,閱讀其源碼是一個很是艱辛的過程。在學習 React 源碼的過程當中,給我幫助最大的就是這個系列文章,因而決定基於這個系列文章談一下本身的理解。本文會大量用到原文中的例子,想體會原汁原味的感受,推薦閱讀原文。javascript
本系列文章基於 React 15.4.2 ,如下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務 - Part 1
React 源碼深度解讀(八):事務 - Part 2
React 源碼深度解讀(九):單個元素更新
React 源碼深度解讀(十):Diff 算法詳解java
在閱讀 React 源碼過程當中,transaction 能夠說無處不在,全部涉及到 UI 更新相關的操做都會藉助 transaction 來完成。下面,咱們就來看看它所起到的特殊所用。算法
Transaction 本質來講只是一個對象,它的核心方法是 perform:segmentfault
perform: function < A, B, C, D, E, F, G, T: (a: A, b: B, c: C, d: D, e: E, f: F) => G // eslint-disable-line space-before-function-paren > ( method: T, scope: any, a: A, b: B, c: C, d: D, e: E, f: F, ): G { var errorThrown; var ret; try { this._isInTransaction = true; // Catching errors makes debugging more difficult, so we start with // errorThrown set to true before setting it to false after calling // close -- if it's still set to true in the finally block, it means // one of these calls threw. errorThrown = true; this.initializeAll(0); 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. this.closeAll(0); } } finally { this._isInTransaction = false; } } return ret; },
能夠看到,這個方法只作了 3 件事情:數組
這裏的結構頗有意思,有 try 居然沒有 catch,取而代之的是 finally。說明就算拋出了錯誤,finally 部分的代碼也要繼續執行,隨後再將錯誤往上層代碼拋。這樣能保證不管在什麼狀況下,closeAll 都能獲得執行。服務器
下面來看一下結構極其類似的 initializeAll 和 closeAll 方法:app
initializeAll: function (startIndex: number): void { var transactionWrappers = this.transactionWrappers; for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; try { // Catching errors makes debugging more difficult, so we start with the // OBSERVED_ERROR state before overwriting it with the real return value // of initialize -- if it's still set to OBSERVED_ERROR in the finally // block, it means wrapper.initialize threw. this.wrapperInitData[i] = OBSERVED_ERROR; this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null; } finally { if (this.wrapperInitData[i] === OBSERVED_ERROR) { // The initializer for wrapper i threw an error; initialize the // remaining wrappers but silence any exceptions from them to ensure // that the first error is the one to bubble up. try { this.initializeAll(i + 1); } catch (err) {} } } } }, ... closeAll: function (startIndex: number): void { var transactionWrappers = this.transactionWrappers; for (var i = startIndex; i < transactionWrappers.length; i++) { var wrapper = transactionWrappers[i]; var initData = this.wrapperInitData[i]; var errorThrown; try { // Catching errors makes debugging more difficult, so we start with // errorThrown set to true before setting it to false after calling // close -- if it's still set to true in the finally block, it means // wrapper.close threw. errorThrown = true; if (initData !== OBSERVED_ERROR && wrapper.close) { wrapper.close.call(this, initData); } errorThrown = false; } finally { if (errorThrown) { // The closer for wrapper i threw an error; close the remaining // wrappers but silence any exceptions from them to ensure that the // first error is the one to bubble up. try { this.closeAll(i + 1); } catch (e) {} } } } this.wrapperInitData.length = 0; },
transactionWrappers 是一個數組,一個 transaction 能夠有多個 wrapper,經過 reinitializeTransaction 來初始化。每一個 wrapper 都須要定義 initialize 和 close 方法。initializeAll 和 closeAll 都能保證其中一個 wrapper 成員拋出錯誤的時候,餘下的 wrapper 能繼續執行。initialize 有一個返回值,傳給對應的 close 方法。當 initialize 拋出錯誤的時候,因爲沒有 catch,exception 會一直往上拋,中斷了ret = method.call(scope, a, b, c, d, e, f)
的執行去到 finally,接着執行 closeAll。異步
瞭解 transaction 的基本概念後,咱們來看下它是怎麼應用的。ide
咱們以ReactDefaultBatchingStrategyTransaction
爲例子來看看 transaction 是怎麼用的:函數
// transaction 子類 function ReactDefaultBatchingStrategyTransaction() { this.reinitializeTransaction(); } // 覆蓋 transaction 的 getTransactionWrappers 方法 Object.assign( ReactDefaultBatchingStrategyTransaction.prototype, Transaction, { getTransactionWrappers: function() { return TRANSACTION_WRAPPERS; }, } ); var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES]; var RESET_BATCHED_UPDATES = { initialize: emptyFunction, close: function() { ReactDefaultBatchingStrategy.isBatchingUpdates = false; }, }; var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates), };
首先,ReactDefaultBatchingStrategyTransaction
繼承了 transaction,並覆蓋了getTransactionWrappers
這個方法來定義本身的 wrapper。這 2 個 wrapper 很簡單,initialize
都是空函數,close 的時候就重置下標誌位,而後再調用另外一個方法。
下面再看一下建立ReactDefaultBatchingStrategyTransaction
的對象ReactDefaultBatchingStrategy
。
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; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // The code is written this way to avoid extra allocations if (alreadyBatchingUpdates) { return callback(a, b, c, d, e); } else { return transaction.perform(callback, null, a, b, c, d, e); } }, };
第一步是建立一個 ReactDefaultBatchingStrategyTransaction 實例。當batchedUpdates
第一次被調用的時候,alreadyBatchingUpdates
爲 false,會調用transaction.perform
,讓後續的操做都處於 transaction 的上下文之中。後面再調用batchedUpdates
的時候,只是單純的執行callback
。
而調用ReactDefaultBatchingStrategy
的是ReactUpdates
,它經過依賴注入的方法在運行的時候將ReactDefaultBatchingStrategy
注入進去。
function enqueueUpdate(component) { ensureInjected(); // Various parts of our code (such as ReactCompositeComponent's // _renderValidatedComponent) assume that calls to render aren't nested; // verify that that's the case. (This is called by each top-level update // function, like setState, forceUpdate, etc.; creation and // destruction of top-level components is guarded in ReactMount.) if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } dirtyComponents.push(component); if (component._updateBatchNumber == null) { component._updateBatchNumber = updateBatchNumber + 1; } }
當enqueueUpdate
第一次執行的時候,它會檢測是否在 batchUpdate 的模式下(batchingStrategy.isBatchingUpdates
),若是不是則調用batchingStrategy.batchedUpdates
,若是是則執行dirtyComponents.push(component)
。
當咱們使用setState
的時候,它會調用ReactUpdates
的enqueueSetState
,而後再調用enqueueUpdate
。若是在 React 的生命週期函數又或者使用 React 自帶的合成事件時,會在setState
以前先將整個處理過程設置爲 batchUpdate 的模式,因此當咱們setState
的時候,實際上只會執行dirtyComponents.push(component)
,並不會立刻更新 state,這就是爲何setState
看似異步更新的緣由。實際上它仍是同步的。
以 React 生命週期函數爲例子,當 Component 被初始化的時候,會執行_renderNewRootComponent
:
_renderNewRootComponent: function ( nextElement, container, shouldReuseMarkup, context ) { ... // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched // according to the current batching strategy. ReactUpdates.batchedUpdates( batchedMountComponentIntoNode, componentInstance, container, shouldReuseMarkup, context ); ... },
在這裏就預先將整個處理過程設置爲 batchUpdate 的模式了,官方的註釋也說明了這點。
咱們再經過一張圖,來總結下 transaction 是怎麼被調用的。