摘要:在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個(gè)系列文章,于是決定基于這個(gè)系列文章談一下自己的理解。說明就算拋出了錯(cuò)誤,部分的代碼也要繼續(xù)執(zhí)行,隨后再將錯(cuò)誤往上層代碼拋。和都能保證其中一個(gè)成員拋出錯(cuò)誤的時(shí)候,余下的能繼續(xù)執(zhí)行。
前言
React 是一個(gè)十分龐大的庫,由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非常艱辛的過程。在學(xué)習(xí) React 源碼的過程中,給我?guī)椭畲蟮木褪沁@個(gè)系列文章,于是決定基于這個(gè)系列文章談一下自己的理解。本文會(huì)大量用到原文中的例子,想體會(huì)原汁原味的感覺,推薦閱讀原文。
本系列文章基于 React 15.4.2 ,以下是本系列其它文章的傳送門:
React 源碼深度解讀(一):首次 DOM 元素渲染 - Part 1
React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2
React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3
React 源碼深度解讀(四):首次自定義組件渲染 - Part 1
React 源碼深度解讀(五):首次自定義組件渲染 - Part 2
React 源碼深度解讀(六):依賴注入
React 源碼深度解讀(七):事務(wù) - Part 1
React 源碼深度解讀(八):事務(wù) - Part 2
React 源碼深度解讀(九):?jiǎn)蝹€(gè)元素更新
React 源碼深度解讀(十):Diff 算法詳解
正文
在閱讀 React 源碼過程中,transaction 可以說無處不在,所有涉及到 UI 更新相關(guān)的操作都會(huì)借助 transaction 來完成。下面,我們就來看看它所起到的特殊所用。
Transaction 核心實(shí)現(xiàn)
Transaction 本質(zhì)來說只是一個(gè)對(duì)象,它的核心方法是 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; },
可以看到,這個(gè)方法只做了 3 件事情:
執(zhí)行初始化方法 initializeAll
執(zhí)行傳入的 callback 方法
執(zhí)行收尾方法 closeAll
這里的結(jié)構(gòu)很有意思,有 try 竟然沒有 catch,取而代之的是 finally。說明就算拋出了錯(cuò)誤,finally 部分的代碼也要繼續(xù)執(zhí)行,隨后再將錯(cuò)誤往上層代碼拋。這樣能保證無論在什么情況下,closeAll 都能得到執(zhí)行。
下面來看一下結(jié)構(gòu)極其相似的 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 是一個(gè)數(shù)組,一個(gè) transaction 可以有多個(gè) wrapper,通過 reinitializeTransaction 來初始化。每個(gè) wrapper 都需要定義 initialize 和 close 方法。initializeAll 和 closeAll 都能保證其中一個(gè) wrapper 成員拋出錯(cuò)誤的時(shí)候,余下的 wrapper 能繼續(xù)執(zhí)行。initialize 有一個(gè)返回值,傳給對(duì)應(yīng)的 close 方法。當(dāng) initialize 拋出錯(cuò)誤的時(shí)候,由于沒有 catch,exception 會(huì)一直往上拋,中斷了ret = method.call(scope, a, b, c, d, e, f)的執(zhí)行去到 finally,接著執(zhí)行 closeAll。
了解 transaction 的基本概念后,我們來看下它是怎么應(yīng)用的。
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這個(gè)方法來定義自己的 wrapper。這 2 個(gè) wrapper 很簡(jiǎn)單,initialize都是空函數(shù),close 的時(shí)候就重置下標(biāo)志位,然后再調(diào)用另一個(gè)方法。
下面再看一下創(chuàng)建ReactDefaultBatchingStrategyTransaction的對(duì)象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); } }, };
第一步是創(chuàng)建一個(gè) ReactDefaultBatchingStrategyTransaction 實(shí)例。當(dāng)batchedUpdates第一次被調(diào)用的時(shí)候,alreadyBatchingUpdates為 false,會(huì)調(diào)用transaction.perform,讓后續(xù)的操作都處于 transaction 的上下文之中。后面再調(diào)用batchedUpdates的時(shí)候,只是單純的執(zhí)行callback。
而調(diào)用ReactDefaultBatchingStrategy的是ReactUpdates,它通過依賴注入的方法在運(yùn)行的時(shí)候?qū)?b>ReactDefaultBatchingStrategy注入進(jìn)去。
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; } }
當(dāng)enqueueUpdate第一次執(zhí)行的時(shí)候,它會(huì)檢測(cè)是否在 batchUpdate 的模式下(batchingStrategy.isBatchingUpdates),如果不是則調(diào)用batchingStrategy.batchedUpdates,如果是則執(zhí)行dirtyComponents.push(component)。
當(dāng)我們使用setState的時(shí)候,它會(huì)調(diào)用ReactUpdates的enqueueSetState,然后再調(diào)用enqueueUpdate。如果在 React 的生命周期函數(shù)又或者使用 React 自帶的合成事件時(shí),會(huì)在setState之前先將整個(gè)處理過程設(shè)置為 batchUpdate 的模式,所以當(dāng)我們setState的時(shí)候,實(shí)際上只會(huì)執(zhí)行dirtyComponents.push(component),并不會(huì)馬上更新 state,這就是為什么setState看似異步更新的原因。實(shí)際上它還是同步的。
以 React 生命周期函數(shù)為例子,當(dāng) Component 被初始化的時(shí)候,會(huì)執(zhí)行_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 ); ... },
在這里就預(yù)先將整個(gè)處理過程設(shè)置為 batchUpdate 的模式了,官方的注釋也說明了這點(diǎn)。
總結(jié)
我們?cè)偻ㄟ^一張圖,來總結(jié)下 transaction 是怎么被調(diào)用的。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98983.html
摘要:前言是一個(gè)十分龐大的庫,由于要同時(shí)考慮和,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非常艱辛的過程。在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個(gè)系列文章,于是決定基于這個(gè)系列文章談一下自己的理解。 前言 React 是一個(gè)十分龐大的庫,由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常...
摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結(jié)構(gòu)如下經(jīng)過編譯后,生成如下代碼構(gòu)建頂層包裝組件跟普通元素渲染一樣,第一步先會(huì)執(zhí)行創(chuàng)建為的。調(diào)用順序已在代碼中注釋。先看圖,這部分內(nèi)容將在下回分解 前言 React 是一個(gè)十分龐大的庫,由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非...
摘要:在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個(gè)系列文章,于是決定基于這個(gè)系列文章談一下自己的理解。到此為止,首次渲染就完成啦總結(jié)從啟動(dòng)到元素渲染到頁面,并不像看起來這么簡(jiǎn)單,中間經(jīng)歷了復(fù)雜的層級(jí)調(diào)用。 前言 React 是一個(gè)十分龐大的庫,由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非常艱辛的過...
摘要:依賴注入和控制反轉(zhuǎn),這兩個(gè)詞經(jīng)常一起出現(xiàn)。一句話表述他們之間的關(guān)系依賴注入是控制反轉(zhuǎn)的一種實(shí)現(xiàn)方式。而兩者有大量的代碼都是可以共享的,這就是依賴注入的使用場(chǎng)景了。下一步就是創(chuàng)建具體的依賴內(nèi)容,然后注入到需要的地方這里的等于這個(gè)對(duì)象。 前言 React 是一個(gè)十分龐大的庫,由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)...
摘要:本文將要講解的調(diào)用棧是下面這個(gè)樣子的平臺(tái)無關(guān)相關(guān)如果看源碼,我們會(huì)留意到很多相關(guān)的代碼,我們暫時(shí)先將其忽略,會(huì)在后續(xù)的文章中進(jìn)行講解。現(xiàn)在我們來看一下各實(shí)例間的關(guān)系目前為止的調(diào)用棧平臺(tái)無關(guān)相關(guān)下一篇講解三總結(jié)本文講解了轉(zhuǎn)化為的過程。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 R...
閱讀 1297·2021-09-27 13:35
閱讀 2605·2021-09-06 15:12
閱讀 3410·2019-08-30 15:55
閱讀 2863·2019-08-30 15:43
閱讀 454·2019-08-29 16:42
閱讀 3470·2019-08-29 15:39
閱讀 3093·2019-08-29 12:28
閱讀 1267·2019-08-29 11:11