成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

React 源碼深度解讀(七):事務(wù) - Part 1

KavenFan / 2708人閱讀

摘要:在學(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)用ReactUpdatesenqueueSetState,然后再調(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

相關(guān)文章

  • React 源碼深度解讀(八):事務(wù) - Part 2

    摘要:前言是一個(gè)十分龐大的庫,由于要同時(shí)考慮和,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非常艱辛的過程。在學(xué)習(xí)源碼的過程中,給我?guī)椭畲蟮木褪沁@個(gè)系列文章,于是決定基于這個(gè)系列文章談一下自己的理解。 前言 React 是一個(gè)十分龐大的庫,由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常...

    airborne007 評(píng)論0 收藏0
  • React 源碼深度解讀(四):首次自定義組件渲染 - Part 1

    摘要:本篇開始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結(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è)非...

    Warren 評(píng)論0 收藏0
  • React 源碼深度解讀(三):首次 DOM 元素渲染 - Part 3

    摘要:在學(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è)非常艱辛的過...

    U2FsdGVkX1x 評(píng)論0 收藏0
  • React 源碼深度解讀(六):依賴注入

    摘要:依賴注入和控制反轉(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í)...

    glumes 評(píng)論0 收藏0
  • React 源碼深度解讀(二):首次 DOM 元素渲染 - Part 2

    摘要:本文將要講解的調(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...

    wean 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<