React-虛擬dom的渲染過程與特性

在熟練使用react中泽疆,聽到最多的就是虛擬dom,diff算法等等梯浪,也是面試必問的一個題目瓢娜,這個問題想要弄透徹眠砾,需要深入閱讀源碼,源碼閱讀還是有一定的難度的柒巫。對這個源碼的理解我也是閱讀很多別人的文章來輔助理解的堡掏,希望也能對看到的人有所幫助。

開發(fā)中常常遇到的問題:

1.為何必須引用React

2.自定義的React組件為何必須大寫

3.React如何防止XSS

4.React的Diff算法

5.key在React中的作用

什么是虛擬dom淤井?

在原生的JavaScript程序中布疼,我們直接對DOM進行創(chuàng)建和更改摊趾,而DOM元素通過我們監(jiān)聽的事件和我們的應(yīng)用程序進行通訊币狠。

所謂的virtual dom游两,也就是虛擬節(jié)點。React會先將你的代碼轉(zhuǎn)換成一個JavaScript對象來模擬DOM中的節(jié)點漩绵,然后再通過特定的render方法將其渲染成真實的DOM節(jié)點贱案。這個JavaScript對象就是虛擬DOM。

比如下面一段 html代碼:

<div class="title">

? ? ? <span>Hello world</span>

? ? ? <ul>

? ? ? ? <li>hello</li>

? ? ? ? <li>world</li>

? ? ? </ul>

</div>

在 React可能存儲為這樣的 JS代碼:

const VitrualDom = {

? type: 'div',

? props: { class: 'title' },

? children: [

? ? {

? ? ? type: 'span',

? ? ? children: 'Hello world'

? ? },

? ? {

? ? ? type: 'ul',

? ? ? children: [

? ? ? ? { type: 'ul', children: 'hello' },

? ? ? ? { type: 'ul', children: 'world' }

? ? ? ]

? ? }

? ]

}

當(dāng)我們需要創(chuàng)建或更新元素時止吐,React首先會讓這個VitrualDom對象進行創(chuàng)建和更改宝踪,然后再將VitrualDom對象渲染成真實DOM;

當(dāng)我們需要對DOM進行事件監(jiān)聽時,首先對VitrualDom進行事件監(jiān)聽厉膀,VitrualDom會代理原生的DOM事件從而做出響應(yīng)。

使用虛擬dom的好處是什么?

1.提升開發(fā)效率

使用JavaScript仗哨,我們在編寫應(yīng)用程序時的關(guān)注點在于如何更新DOM。

使用React,你只需要告訴React你想讓視圖處于什么狀態(tài)雏节,React則通過VitrualDom確保DOM與該狀態(tài)相匹配怔锌。你不必自己去完成屬性操作涝涤、事件處理、DOM更新辨宠,React會替你完成這一切。

這讓我們更關(guān)注我們的業(yè)務(wù)邏輯而非DOM操作,這一點即可大大提升我們的開發(fā)效率毡惜。

2.性能提升

直接操作 DOM是非常耗費性能的帕膜,這一點毋庸置疑。VitrualDom的優(yōu)勢在于 React的 Diff算法和批處理策略荒典, React在頁面更新之前刻剥,提前計算好了如何進行更新和渲染 DOM御吞。實際上挟裂,這個計算過程我們在直接操作?DOM時嫩与,也是可以自己判斷和實現(xiàn)的处坪,但是一定會耗費非常多的精力和時間玄帕,而且往往我們自己做的是不如 React好的丧没。所以,在這個過程中?React幫助我們"提升了性能"奸汇。

React基于 VitrualDom自己實現(xiàn)了一套自己的事件機制,自己模擬了事件冒泡和捕獲的過程,采用了事件代理柬采,批量更新等方法礁遣,抹平了各個瀏覽器的事件兼容性問題盈包。

虛擬DOM原理、特性

React組件的渲染流程:

1.使用 React.createElement或 JSX編寫 React組件叛氨,實際上所有的 JSX代碼最后都會轉(zhuǎn)換成 React.createElement(...)寞埠, Babel幫助我們完成了這個轉(zhuǎn)換的過程仁连。因為我們的 JSX 代碼會被 Babel 編譯為React.createElement伍伤,不引入 React 的話就不能使用React.createElement了。

2.createElement函數(shù)對 key和 ref等特殊的 props進行處理劝评,并獲取 defaultProps對默認(rèn) props進行賦值,并且對傳入的孩子節(jié)點進行處理均牢,最終構(gòu)造成一個 ReactElement對象(所謂的虛擬 DOM)徘跪。

3.ReactDOM.render將生成好的虛擬 DOM渲染到指定容器上松邪,其中采用了批處理、事務(wù)等機制并且對特定瀏覽器進行了性能優(yōu)化,最終轉(zhuǎn)換為真實 DOM。

虛擬DOM的組成

即ReactElementelement對象参萄,我們的組件最終會被渲染成下面的結(jié)構(gòu):

type:元素的類型怜奖,可以是原生html類型(字符串)募强,或者自定義組件(函數(shù)或class)

key:組件的唯一標(biāo)識,用于Diff算法崇摄,下面會詳細(xì)介紹

ref:用于訪問原生dom節(jié)點

props:傳入組件的props擎值,chidren是props中的一個屬性,它存儲了當(dāng)前組件的孩子節(jié)點逐抑,可以是數(shù)組(多個孩子節(jié)點)或?qū)ο螅ㄖ挥幸粋€孩子節(jié)點)

owner:當(dāng)前正在構(gòu)建的Component所屬的Component

self:(非生產(chǎn)環(huán)境)指定當(dāng)前位于哪個組件實例

_source:(非生產(chǎn)環(huán)境)指定調(diào)試代碼來自的文件(fileName)和代碼行數(shù)(lineNumber)

自定義的React組件為何必須大寫

我們在 React 中都是寫的 JSX語法鸠儿,從 JSX語法 到頁面上的 真實DOM大概需要經(jīng)歷以下幾個階段:JSX語法 —> 虛擬DOM(JS對象) —> 真實DOM。

因為瀏覽器是無法識別JSX語法的厕氨,因此我們需要通過?babel 對JSX語法進行轉(zhuǎn)義进每,然后才能生成虛擬DOM對象,而原因就是在這里命斧。babel在進行轉(zhuǎn)義JSX語法時田晚,是調(diào)用了 React.createElement() 這個方法,這個方法需要接收三個參數(shù):type, config, children国葬。第一個參數(shù)聲明了這個元素的類型贤徒。

創(chuàng)建自定義組件時沒有首字母大寫,而 babel 在轉(zhuǎn)義時把它當(dāng)成了一個字符串 傳遞汇四;把首字母大寫接奈,babel 在轉(zhuǎn)義時傳遞了一個變量進去。

問題就在這里通孽,如果傳遞的是一個字符串序宦,那么在創(chuàng)建虛擬DOM對象時,React會認(rèn)為這是一個簡單的HTML標(biāo)簽利虫,但是這顯然不是一個簡單的HTML標(biāo)簽挨厚,因此去創(chuàng)建一個不存在的標(biāo)簽肯定是會報錯的。

如果首字母大寫糠惫,那么就會當(dāng)成一個變量傳遞進去疫剃,這個時候React會知道這是一個自定義組件,因此他就不會報錯了硼讽。

防止XSS

ReactElement對象還有一個$$typeof屬性巢价,它是一個Symbol類型的變量Symbol.for('react.element'),當(dāng)環(huán)境不支持Symbol時,$$typeof被賦值為0xeac7壤躲。

這個變量可以防止XSS城菊。如果你的服務(wù)器有一個漏洞,允許用戶存儲任意JSON對象碉克,?而客戶端代碼需要一個字符串凌唬,這可能為你的應(yīng)用程序帶來風(fēng)險。JSON中不能存儲Symbol類型的變量漏麦,而React渲染時會把沒有$$typeof標(biāo)識的組件過濾掉客税。

虛擬DOM事件機制

React自己實現(xiàn)了一套事件機制,其將所有綁定在虛擬 DOM上的事件映射到真正的 DOM事件撕贞,并將所有的事件都代理到 document上更耻,自己模擬了事件冒泡和捕獲的過程,并且進行統(tǒng)一的事件分發(fā)捏膨。

React自己構(gòu)造了合成事件對象SyntheticEvent秧均,這是一個跨瀏覽器原生事件包裝器。?它具有與瀏覽器原生事件相同的接口号涯,包括stopPropagation()和preventDefault()等等目胡,在所有瀏覽器中他們工作方式都相同。這抹平了各個瀏覽器的事件兼容性問題诚隙。

React的diff算法

React將DOM抽象為虛擬DOM, 然后通過新舊虛擬DOM 這兩個對象的差異(Diff算法),最終只把變化的部分重新渲染,提高渲染效率的過程; diff 是通過JS層面的計算讶隐,返回一個patch對象,即補丁對象久又,在通過特定的操作解析patch對象,完成頁面的重新渲染效五。是在render里面進行計算的地消。

(1)什么是調(diào)和?將Virtual DOM樹轉(zhuǎn)換成actual DOM樹的最少操作的過程稱為 調(diào)和 畏妖。

(2)什么是React diff算法脉执?diff算法是調(diào)和的具體實現(xiàn)。

Dom diff算法解析

DIFF算法在執(zhí)行時有三個維度戒劫,分別是Tree DIFF半夷、Component DIFF和Element DIFF,執(zhí)行時按順序依次執(zhí)行迅细,它們的差異僅僅因為DIFF粒度不同巫橄、執(zhí)行先后順序不同。diff算法用將O(n^3)復(fù)雜度轉(zhuǎn)化為?O(n)復(fù)雜度茵典。

tree diff

Tree DIFF是對樹的每一層進行遍歷湘换,如果某組件不存在了,則會直接銷毀。如圖所示彩倚,左邊是舊屬筹我,右邊是新屬,第一層是R組件帆离,一模一樣蔬蕊,不會發(fā)生變化;第二層進入Component DIFF哥谷,同一類型組件繼續(xù)比較下去岸夯,發(fā)現(xiàn)A組件沒有,所以直接刪掉A呼巷、B囱修、C組件;繼續(xù)第三層王悍,重新創(chuàng)建A破镰、B、C組件压储。

Component DIFF

如圖所示鲜漩,第一層遍歷完,進行第二層遍歷時集惋,D和G組件是不同類型的組件孕似,不同類型組件直接進行替換,將D刪掉刮刑,再將G重建喉祭。

Element DIFF

Element DIFF緊接著以上統(tǒng)一類型組件繼續(xù)比較下去,常見類型就是列表雷绢。同一個列表由舊變新有三種行為泛烙,插入、移動和刪除翘紊,它的比較策略是對于每一個列表指定key蔽氨,先將所有列表遍歷一遍,確定要新增和刪除的帆疟,再確定需要移動的鹉究。如圖所示,第一步將D刪掉踪宠,第二步增加E自赔,再次執(zhí)行時A和B只需要移動位置即可。

總結(jié):

1.React是如何將O(n3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 的殴蓬?

????????只進行同級比較

????????不同類的React組件會被當(dāng)做完全不同的DOM結(jié)構(gòu)而被完全替換

????????key prop:開發(fā)人員可以通過給Virtual DOM一個唯一的key屬性暗示React這是同一個DOM結(jié)構(gòu)匿级,反之若key值不同則會被當(dāng)做完全不同的DOM結(jié)構(gòu)蟋滴。

2.React通過分層求異的策略,對tree diff進行算法優(yōu)化痘绎;

3.React通過相同類生成相似樹形結(jié)構(gòu)津函,不同類生成不同樹形結(jié)構(gòu)的策略,對component diff進行算法優(yōu)化孤页。

4.React通過設(shè)置唯一key的策略尔苦,對element diff進行算法優(yōu)化;

5.建議行施,在開發(fā)組件時允坚,保持穩(wěn)定的DOM結(jié)構(gòu)會有助于性能的提升;

6.建議蛾号,在開發(fā)過程中稠项,盡量減少類似將最后一個節(jié)點移動到列表首部的操作,當(dāng)節(jié)點數(shù)量過大或更新操作過于頻繁時鲜结,在一定程度上會影響React的渲染性能展运。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市精刷,隨后出現(xiàn)的幾起案子拗胜,更是在濱河造成了極大的恐慌,老刑警劉巖怒允,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件埂软,死亡現(xiàn)場離奇詭異,居然都是意外死亡纫事,警方通過查閱死者的電腦和手機勘畔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丽惶,“玉大人咖杂,你說我怎么就攤上這事∥梅颍” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵懦尝,是天一觀的道長知纷。 經(jīng)常有香客問我,道長陵霉,這世上最難降的妖魔是什么琅轧? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮踊挠,結(jié)果婚禮上乍桂,老公的妹妹穿的比我還像新娘冲杀。我一直安慰自己,他們只是感情好睹酌,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布权谁。 她就那樣靜靜地躺著,像睡著了一般憋沿。 火紅的嫁衣襯著肌膚如雪旺芽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天辐啄,我揣著相機與錄音采章,去河邊找鬼。 笑死壶辜,一個胖子當(dāng)著我的面吹牛悯舟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砸民,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼抵怎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阱洪?” 一聲冷哼從身側(cè)響起便贵,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冗荸,沒想到半個月后承璃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蚌本,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年盔粹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片程癌。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡舷嗡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嵌莉,到底是詐尸還是另有隱情进萄,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布锐峭,位于F島的核電站中鼠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沿癞。R本人自食惡果不足惜援雇,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椎扬。 院中可真熱鬧惫搏,春花似錦具温、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至川陆,卻和暖如春剂习,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背较沪。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工鳞绕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尸曼。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓们何,卻偏偏與公主長得像,于是被迫代替她去往敵國和親控轿。 傳聞我的和親對象是個殘疾皇子冤竹,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350