React Diff原理深入分析
在了解Diff前,先看下React的虛擬DOM的結構
這是html結構
<div id="father"> <p class="child">I am child p</p> <div class="child">I am child div</div> </div>
這是React渲染html時的js代碼 自己可以在babel上試試
React.createElement("div", {id: "father"}, React.createElement("p", {class: "child"}, "I am child p"), React.createElement("div", {class: "child"}, "I am child div") );
由此可以看出這是一個樹結構
React在調用render方法時會創(chuàng)建一顆樹(簡稱pre),在下一次調用render方法時會返回一顆不同的樹(簡稱cur)。React就會比較pre和cur這兩棵樹之間的差別來判斷如何高效的更新UI,保證當前UI與最新的樹cur保持同步。
為了高效更新UI,React在以下兩個假設的基礎上提出了一套O(n)的啟發(fā)算法:
1.兩個不同類型的元素會產(chǎn)生出不同的樹;
2.開發(fā)者可以通過設置 key 屬性,來告知渲染哪些子元素在不同的渲染下可以保存不變;
Diffing 算法
逐層比較
在對比兩棵樹時,React是逐層進行比較的,只會對相同顏色框內的DOM節(jié)點進行比較。
首先比較兩棵樹的根節(jié)點,不同類型的根節(jié)點會有不同的形態(tài)。當根節(jié)點為不同類型的元素時,React 會拆卸原有的樹并且建立起新的樹。舉個例子,當一個元素從 <a> 變成 <img>,從 <Article> 變成 <Comment>,或從 <Button> 變成 <div> 都會觸發(fā)一個完整的重建流程。
//before <div> <App/> </div> //after <p> <App/> </p>
React會銷毀App組件(該組件的子組件也全都銷毀),并且重新創(chuàng)建一個新的App組件(也包括App的子組件)。
如下的DOM結構轉換:
React只會簡單的考慮同層節(jié)點的位置變換,對于不同層的節(jié)點,只有簡單的創(chuàng)建和刪除。當根節(jié)點發(fā)現(xiàn)子節(jié)點中A不見了,就會直接銷毀A;而當D發(fā)現(xiàn)自己多了一個子節(jié)點A,則會創(chuàng)建一個新的A作為子節(jié)點。因此對于這種結構的轉變的實際操作是:
A.destroy(); A = new A(); A.append(new B()); A.append(new C()); D.append(A);
雖然看上去這樣的算法有些“簡陋”,但是其基于的是第一個假設:兩個不同類型的元素會產(chǎn)生出不同的樹。根據(jù)React官方文檔,這一假設至今為止沒有導致嚴重的性能問題。這當然也給我們一個提示,在實現(xiàn)自己的組件時,保持穩(wěn)定的DOM結構會有助于性能的提升。例如,我們有時可以通過CSS隱藏或顯示某些節(jié)點,而不是真的移除或添加DOM節(jié)點。
對比同類型的組件元素
當一個組件更新時,組件實例會保持不變,但是state或props中數(shù)據(jù)的變化會調用render從而改變該組件中的子元素進行更新。
對比同一類型的元素
當對比兩個相同類型的 React 元素時,React 會保留 DOM 節(jié)點,僅比對及更新有改變的屬性。
<div className="before" title="stuff" /> <div className="after" title="stuff" />
React將div的className由before修改為after(類似于React中更新state的合并操作)。
對子節(jié)點進行遞歸
默認情況下(逐層比較),當遞歸 DOM 節(jié)點的子元素時,React 會同時遍歷兩個子元素的列表;當產(chǎn)生差異時,生成一個 mutation(突變)。
所以在列表末尾新增元素時,更新開銷比較小。例如:
<ul> <li>first</li> <li>second</li> </ul> <ul> <li>first</li> <li>second</li> <li>third</li> </ul>
React 會先匹配兩個 <li>first</li> 對應的樹,然后匹配第二個元素 <li>second</li> 對應的樹,最后插入第三個元素的 <li>third</li> 樹。
如果只是簡單的將新增元素插入到表頭,那么更新開銷會比較大。比如:
<ul> <li>Duke</li> <li>Villanova</li> </ul> <ul> <li>Connecticut</li> <li>Duke</li> <li>Villanova</li> </ul>
React 并不會意識到應該保留 <li>Duke</li> 和 <li>Villanova</li>,而是會重建每一個子元素。這種情況會帶來性能問題。
Keys
為了解決上述問題,React 引入了 key 屬性。當子元素擁有 key 時,React 使用 key 來匹配原有樹上的子元素以及最新樹上的子元素。以下示例在新增 key 之后,使得樹的轉換效率得以提高:
<ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> <ul> <li key="2014">Connecticut</li> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul>
現(xiàn)在 React 知道只有帶著 '2014' key 的元素是新元素,帶著 '2015' 以及 '2016' key 的元素僅僅移動了。所以只是創(chuàng)建了 key=2014的元素,并不會創(chuàng)建剩下的兩個元素。
所以key的取值最好不要使用數(shù)組的下標,因為數(shù)組的順序可能會發(fā)生變化,最好使用自身數(shù)據(jù)攜帶的唯一標識(id或是其它的屬性)。
1. 虛擬DOM中key的作用:
1). 簡單的說: key是虛擬DOM對象的標識, 在更新顯示時key起著極其重要的作用。
2). 詳細的說: 當狀態(tài)中的數(shù)據(jù)發(fā)生變化時,react會根據(jù)【新數(shù)據(jù)】生成【新的虛擬DOM】, 隨后React進行【新虛擬DOM】與【舊虛擬DOM】的diff比較,比較規(guī)則如下:
a. 舊虛擬DOM中找到了與新虛擬DOM相同的key:
(1).若虛擬DOM中內容沒變, 直接使用之前的真實DOM
(2).若虛擬DOM中內容變了, 則生成新的真實DOM,隨后替換掉頁面中之前的真實DOM
b. 舊虛擬DOM中未找到與新虛擬DOM相同的key
根據(jù)數(shù)據(jù)創(chuàng)建新的真實DOM,隨后渲染到到頁面
2. 用index作為key可能會引發(fā)的問題:
1. 若對數(shù)據(jù)進行:逆序添加、逆序刪除等破壞順序操作:
會產(chǎn)生沒有必要的真實DOM更新 ==> 界面效果沒問題, 但效率低。
2. 如果結構中還包含輸入類的DOM:
會產(chǎn)生錯誤DOM更新 ==> 界面有問題。
3. 注意!如果不存在對數(shù)據(jù)的逆序添加、逆序刪除等破壞順序操作,
僅用于渲染列表用于展示,使用index作為key是沒有問題的。
以上就是React Diff原理深入分析的詳細內容,更多關于React Diff原理的資料請關注腳本之家其它相關文章!
相關文章
詳解開發(fā)react應用最好用的腳手架 create-react-app
本篇文章主要介紹了詳解開發(fā)react應用最好用的腳手架 create-react-app,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04react koa rematch 如何打造一套服務端渲染架子
這篇文章主要介紹了react koa rematch 如何打造一套服務端渲染架子,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-06-06使用react+redux實現(xiàn)計數(shù)器功能及遇到問題
使用redux管理數(shù)據(jù),由于Store獨立于組件,使得數(shù)據(jù)管理獨立于組件,解決了組件之間傳遞數(shù)據(jù)困難的問題,非常好用,今天重點給大家介紹使用react+redux實現(xiàn)計數(shù)器功能及遇到問題,感興趣的朋友參考下吧2021-06-06基于visual studio code + react 開發(fā)環(huán)境搭建過程
今天通過本文給大家分享基于visual studio code + react 開發(fā)環(huán)境搭建過程,本文給大家介紹的非常詳細,包括react安裝問題及安裝 Debugger for Chrome的方法,需要的朋友跟隨小編一起看看吧2021-07-07