<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                React中最神奇的部分莫過于虛擬DOM,以及其高效的Diff算法。這讓我們可以無需擔心性能問題而”毫無顧忌”的隨時“刷新”整個頁面,由虛擬DOM來確保只對界面上真正變化的部分進行實際的DOM操作。React在這一部分已經做到足夠透明,在實際開發中我們基本無需關心虛擬DOM是如何運作的。然而,作為有態度的程序員,我們總是對技術背后的原理充滿著好奇。理解其運行機制不僅有助于更好的理解React組件的生命周期,而且對于進一步優化React程序也會有很大幫助。 [TOC] ## 什么是DOM Diff算法 Web界面由DOM樹來構成,當其中某一部分發生變化時,其實就是對應的某個DOM節點發生了變化。在React中,構建UI界面的思路是由當前狀態決定界面。前后兩個狀態就對應兩套界面,然后由React來比較兩個界面的區別,這就需要對DOM樹進行Diff算法分析。 即給定任意兩棵樹,找到最少的轉換步驟。但是[標準的的Diff算法](http://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf)復雜度需要O(n^3),這顯然無法滿足性能要求。要達到每次界面都可以整體刷新界面的目的,勢必需要對算法進行優化。這看上去非常有難度,然而Facebook工程師卻做到了,他們結合Web界面的特點做出了兩個簡單的假設,使得Diff算法復雜度直接降低到O(n) 1. 兩個相同組件產生類似的DOM結構,不同的組件產生不同的DOM結構; 2. 對于同一層次的一組子節點,它們可以通過唯一的id進行區分。 算法上的優化是React整個界面Render的基礎,事實也證明這兩個假設是合理而精確的,保證了整體界面構建的性能。 ## 不同節點類型的比較 為了在樹之間進行比較,我們首先要能夠比較兩個節點,在React中即比較兩個虛擬DOM節點,當兩個節點不同時,應該如何處理。這分為兩種情況:(1)節點類型不同 ,(2)節點類型相同,但是屬性不同。本節先看第一種情況。 當在樹中的同一位置前后輸出了不同類型的節點,React直接刪除前面的節點,然后創建并插入新的節點。假設我們在樹的同一位置前后兩次輸出不同類型的節點。 ~~~ renderA: <div /> renderB: <span /> => [removeNode <div />], [insertNode <span />] ~~~ 當一個節點從div變成span時,簡單的直接刪除div節點,并插入一個新的span節點。這符合我們對真實DOM操作的理解。 需要注意的是,刪除節點意味著徹底銷毀該節點,而不是再后續的比較中再去看是否有另外一個節點等同于該刪除的節點。如果該刪除的節點之下有子節點,那么這些子節點也會被完全刪除,它們也不會用于后面的比較。這也是算法復雜能夠降低到O(n)的原因。 上面提到的是對虛擬DOM節點的操作,而同樣的邏輯也被用在React組件的比較,例如: ~~~ renderA: <Header /> renderB: <Content /> => [removeNode <Header />], [insertNode <Content />] ~~~ 當React在同一個位置遇到不同的組件時,也是簡單的銷毀第一個組件,而把新創建的組件加上去。這正是應用了第一個假設,不同的組件一般會產生不一樣的DOM結構,與其浪費時間去比較它們基本上不會等價的DOM結構,還不如完全創建一個新的組件加上去。 由這一React對不同類型的節點的處理邏輯我們很容易得到推論,那就是React的DOM Diff算法實際上只會對樹進行逐層比較,如下所述。 ## 逐層進行節點比較 提到樹,相信大多數同學立刻想到的是二叉樹,遍歷,最短路徑等復雜的數據結構算法。而在React中,樹的算法其實非常簡單,那就是兩棵樹只會對同一層次的節點進行比較。如下圖所示: ![](https://box.kancloud.cn/2015-09-24_5603ab8f98184.png) React只會對相同顏色方框內的DOM節點進行比較,即同一個父節點下的所有子節點。當發現節點已經不存在,則該節點及其子節點會被完全刪除掉,不會用于進一步的比較。這樣只需要對樹進行一次遍歷,便能完成整個DOM樹的比較。 例如,考慮有下面的DOM結構轉換: ![](https://box.kancloud.cn/2015-09-24_5603ab910ce37.png) A節點被整個移動到D節點下,直觀的考慮DOM Diff操作應該是 ~~~ A.parent.remove(A); D.append(A); ~~~ 但因為React只會簡單的考慮同層節點的位置變換,對于不同層的節點,只有簡單的創建和刪除。當根節點發現子節點中A不見了,就會直接銷毀A;而當D發現自己多了一個子節點A,則會創建一個新的A作為子節點。因此對于這種結構的轉變的實際操作是: ~~~ A.destroy(); A = new A(); A.append(new B()); A.append(new C()); D.append(A); ~~~ 可以看到,以A為根節點的樹被整個重新創建。 雖然看上去這樣的算法有些“簡陋”,但是其基于的是第一個假設:兩個不同組件一般產生不一樣的DOM結構。根據[React官方博客](http://facebook.github.io/react/docs/reconciliation.html),這一假設至今為止沒有導致嚴重的性能問題。這當然也給我們一個提示,在實現自己的組件時,保持穩定的DOM結構會有助于性能的提升。例如,我們有時可以通過CSS隱藏或顯示某些節點,而不是真的移除或添加DOM節點。 ## 由DOM Diff算法理解組件的生命周期 在[上一篇文章](http://www.infoq.com/cn/articles/react-jsx-and-component)中介紹了React組件的生命周期,其中的每個階段其實都是和DOM Diff算法息息相關的。例如以下幾個方法: * constructor: 構造函數,組件被創建時執行; * componentDidMount: 當組件添加到DOM樹之后執行; * componentWillUnmount: 當組件從DOM樹中移除之后執行,在React中可以認為組件被銷毀; * componentDidUpdate: 當組件更新時執行。 為了演示組件生命周期和DOM Diff算法的關系,筆者創建了一個示例:[https://supnate.github.io/react-dom-diff/index.html](https://supnate.github.io/react-dom-diff/index.html)?,大家可以直接訪問試用。這時當DOM樹進行如下轉變時,即從“shape1”轉變到“shape2”時。我們來觀察這幾個方法的執行情況: ![](https://box.kancloud.cn/2015-09-24_5603ab926ee60.png) 瀏覽器開發工具控制臺輸出如下結果: ~~~ C will unmount. C is created. B is updated. A is updated. C did mount. D is updated. R is updated. ~~~ 可以看到,C節點是完全重建后再添加到D節點之下,而不是將其“移動”過去。如果大家有興趣,也可以fork示例代碼:[https://github.com/supnate/react-dom-diff](https://github.com/supante/react-dom-diff)[?](https://github.com/supnate/react-dom-diff)。從而可以自己添加其它樹結構,試驗它們之間是如何轉換的。 ## 相同類型節點的比較 第二種節點的比較是相同類型的節點,算法就相對簡單而容易理解。React會對屬性進行重設從而實現節點的轉換。例如: ~~~ renderA: <div id="before" /> renderB: <div id="after" /> => [replaceAttribute id "after"] ~~~ 虛擬DOM的style屬性稍有不同,其值并不是一個簡單字符串而必須為一個對象,因此轉換過程如下: ~~~ renderA: <div style={{color: 'red'}} /> renderB: <div style={{fontWeight: 'bold'}} /> => [removeStyle color], [addStyle font-weight 'bold'] ~~~ ## 列表節點的比較 上面介紹了對于不在同一層的節點的比較,即使它們完全一樣,也會銷毀并重新創建。那么當它們在同一層時,又是如何處理的呢?這就涉及到列表節點的Diff算法。相信很多使用React的同學大多遇到過這樣的警告: ![](https://box.kancloud.cn/2015-09-24_5603ab935b2d2.png) 這是React在遇到列表時卻又找不到key時提示的警告。雖然無視這條警告大部分界面也會正確工作,但這通常意味著潛在的性能問題。因為React覺得自己可能無法高效的去更新這個列表。 列表節點的操作通常包括添加、刪除和排序。例如下圖,我們需要往B和C直接插入節點F,在jQuery中我們可能會直接使用$(B).after(F)來實現。而在React中,我們只會告訴React新的界面應該是A-B-F-C-D-E,由Diff算法完成更新界面。 ![](https://box.kancloud.cn/2015-09-24_5603ab959005c.png) 這時如果每個節點都沒有唯一的標識,React無法識別每一個節點,那么更新過程會很低效,即,將C更新成F,D更新成C,E更新成D,最后再插入一個E節點。效果如下圖所示: ![](https://box.kancloud.cn/2015-09-24_5603ab968b258.png) 可以看到,React會逐個對節點進行更新,轉換到目標節點。而最后插入新的節點E,涉及到的DOM操作非常多。而如果給每個節點唯一的標識(key),那么React能夠找到正確的位置去插入新的節點,入下圖所示: ![](https://box.kancloud.cn/2015-09-24_5603ab976202e.png) 對于列表節點順序的調整其實也類似于插入或刪除,下面結合示例代碼我們看下其轉換的過程。仍然使用前面提到的示例:[https://supnate.github.io/react-dom-diff/index.html](https://supnate.github.io/react-dom-diff/index.html)?,我們將樹的形態從shape5轉換到shape6: ![](https://box.kancloud.cn/2015-09-24_5603ab97f00db.png) 即將同一層的節點位置進行調整。如果未提供key,那么React認為B和C之后的對應位置組件類型不同,因此完全刪除后重建,控制臺輸出如下: ~~~ B will unmount. C will unmount. C is created. B is created. C did mount. B did mount. A is updated. R is updated. ~~~ 而如果提供了key,如下面的代碼: ~~~ shape5: function() { return ( <Root> <A> <B key="B" /> <C key="C" /> </A> </Root> ); }, shape6: function() { return ( <Root> <A> <C key="C" /> <B key="B" /> </A> </Root> ); }, ~~~ 那么控制臺輸出如下: ~~~ C is updated. B is updated. A is updated. R is updated. ~~~ 可以看到,對于列表節點提供唯一的key屬性可以幫助React定位到正確的節點進行比較,從而大幅減少DOM操作次數,提高了性能。 ## 小結 本文分析了React的DOM Diff算法究竟是如何工作的,其復雜度控制在了O(n),這讓我們考慮UI時可以完全基于狀態來每次render整個界面而無需擔心性能問題,簡化了UI開發的復雜度。而算法優化的基礎是文章開頭提到的兩個假設,以及React的UI基于組件這樣的一個機制。理解虛擬DOM Diff算法不僅能夠幫助我們理解組件的生命周期,而且也對我們實現自定義組件時如何進一步優化性能具有指導意義。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看