摘要:在學習源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。說明就算拋出了錯誤,部分的代碼也要繼續執行,隨后再將錯誤往上層代碼拋。和都能保證其中一個成員拋出錯誤的時候,余下的能繼續執行。
前言
React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學習 React 源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。本文會大量用到原文中的例子,想體會原汁原味的感覺,推薦閱讀原文。
本系列文章基于 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 算法詳解
正文
在閱讀 React 源碼過程中,transaction 可以說無處不在,所有涉及到 UI 更新相關的操作都會借助 transaction 來完成。下面,我們就來看看它所起到的特殊所用。
Transaction 核心實現
Transaction 本質來說只是一個對象,它的核心方法是 perform:
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 件事情:
執行初始化方法 initializeAll
執行傳入的 callback 方法
執行收尾方法 closeAll
這里的結構很有意思,有 try 竟然沒有 catch,取而代之的是 finally。說明就算拋出了錯誤,finally 部分的代碼也要繼續執行,隨后再將錯誤往上層代碼拋。這樣能保證無論在什么情況下,closeAll 都能得到執行。
下面來看一下結構極其相似的 initializeAll 和 closeAll 方法:
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 的基本概念后,我們來看下它是怎么應用的。
ReactDefaultBatchingStrategyTransaction
我們以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 是怎么被調用的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/98983.html
摘要:前言是一個十分龐大的庫,由于要同時考慮和,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過程。在學習源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常...
摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結構如下經過編譯后,生成如下代碼構建頂層包裝組件跟普通元素渲染一樣,第一步先會執行創建為的。調用順序已在代碼中注釋。先看圖,這部分內容將在下回分解 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非...
摘要:在學習源碼的過程中,給我幫助最大的就是這個系列文章,于是決定基于這個系列文章談一下自己的理解。到此為止,首次渲染就完成啦總結從啟動到元素渲染到頁面,并不像看起來這么簡單,中間經歷了復雜的層級調用。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級非常深,閱讀其源碼是一個非常艱辛的過...
摘要:依賴注入和控制反轉,這兩個詞經常一起出現。一句話表述他們之間的關系依賴注入是控制反轉的一種實現方式。而兩者有大量的代碼都是可以共享的,這就是依賴注入的使用場景了。下一步就是創建具體的依賴內容,然后注入到需要的地方這里的等于這個對象。 前言 React 是一個十分龐大的庫,由于要同時考慮 ReactDom 和 ReactNative ,還有服務器渲染等,導致其代碼抽象化程度很高,嵌套層級...
摘要:本文將要講解的調用棧是下面這個樣子的平臺無關相關如果看源碼,我們會留意到很多相關的代碼,我們暫時先將其忽略,會在后續的文章中進行講解。現在我們來看一下各實例間的關系目前為止的調用棧平臺無關相關下一篇講解三總結本文講解了轉化為的過程。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 R...
閱讀 1264·2021-09-27 13:35
閱讀 2563·2021-09-06 15:12
閱讀 3380·2019-08-30 15:55
閱讀 2829·2019-08-30 15:43
閱讀 432·2019-08-29 16:42
閱讀 3446·2019-08-29 15:39
閱讀 3062·2019-08-29 12:28
閱讀 1239·2019-08-29 11:11