1. 程式人生 > 其它 >props, state與render函式關係 – 資料和頁面是如何實現互相聯動的?

props, state與render函式關係 – 資料和頁面是如何實現互相聯動的?

react是由資料驅動的框架,當資料發生變化頁面就會自動的發生變化。它背後的原理,,, 資料和頁面聯動的機理

當元件的state或者props發生改變的時候,render函式就會重新執行,頁面就會從新被渲染,因為頁面是由render函式渲染出來的。同時,當父元件的render函式被執行時,它的子元件的render都將被重新執行一次

什麼是虛擬DOM
加入沒有react,我們自己實現這個功能,思路大概是: 1,state 資料 2,JSX模板 3,資料 + 模板結合,生成真實的DOM,顯示在頁面上 4,state 發生改變 5,資料 + 模板 結合,生成真實的DOM,替換原始的DOM(這裡耗費了大量的效能) 缺陷: 第一次生成了一個完整的DOM片段 第二次生成了一個完整的DOM片段 第二次的DOM替換第一次的DOM,非常耗效能。

改良: 1,state 資料 2,JSX模板 3,資料 + 模板結合,生成真實的DOM,顯示在頁面上 4,state 發生改變 5,資料 + 模板 結合,生成真實的DOM,並不直接替換原始的DOM 6,新的DOM(這個DOM是JS底層裡的DocumentFragment叫文件碎片,它是在記憶體裡,並沒有被真實地掛載到頁面) 和 原始的DOM 做比對,找差異(DOM層在做比對,耗效能) 7,找出input框發生了變化 8,只用新的DOM中的input元素,替換掉老的DOM中的input元素 這種改良雖然多了幾個步驟,但是效能上確實有了提升,因為在用新的DOM替換老的DOM的時候,涉及到替換DOM的操作或者DOM比對的操作是比較耗效能的,所以第五步替換整個原始的DOM耗費了大量的效能,這裡新的做法是不去替換原始的DOM,而是拿新的DOM和原始的DOM做比對,只需替換原始DOM改變後的input一小部分,這樣DOM替換的內容變少了,這塊效能得到了提升。但是問題又來了,雖然這裡節約了DOM替換的效能,但是又損耗了新的DOM和原始DOM做比對的效能。這樣的效能的提升並不是很明顯。 缺陷: 效能的提升並不是很明顯

react提出了第三種方案,就是虛擬DOM方案: 1,state 資料 2,JSX模板 3,資料 + 模板結合,生成真實的DOM,顯示在頁面上

hello world

4,資料 + 模板 結合 ,生成虛擬DOM(虛擬DOM就是一個JS物件,用它來描述真實DOM)(雖然這裡生成了虛擬DOM,但是這裡的效能損耗極小,用js生成一個js物件它的代價是很小的,但是用js生成一個DOM元素它的代價極高,因為底層原理是,js建立一個js物件其實很簡單,但是建立一個DOM的時候它要呼叫web application級別的api,這種級別的api它的效能損耗是比較大的)

['div',{id:'abc'},['span',{},'hello world']]

5,state 發生改變 6,資料 + 模板 結合 ,生成新的虛擬DOM(極大地提升了效能)

['div',{id:'abc'},['span',{},'happy chen']]

7,比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中的內容(比較原始js物件和新的js物件,兩個js的比對是不消耗效能的,和前面的DOM結構比對相比極大地提升了效能) 8,直接操作DOM,改變span中的內容

總結: react中引入虛擬DOM,減少了真實DOM的建立以及真實DOM的比對,取而代之我建立的都是js物件,比對的也都是物件,通過這種方式react底層極大地提高了效能。本質上就是js裡面去比較js物件不怎麼耗效能,但是比較真實的DOM會很耗效能。

實際上react底層真正的實現第三步和第四步是反過來的,

深入瞭解虛擬DOM
react提出了第三種方案,就是虛擬DOM方案:

1,state 資料 2,JSX模板 3,資料 + 模板 結合 ,生成虛擬DOM

['div',{id:'abc'},['span',{},'hello world']]

4,用虛擬DOM的結構,生成真實的DOM,顯示在頁面上

<div id="abc"><span>hello world</span></div>

5,state 發生改變 6,資料 + 模板 結合 ,生成新的虛擬DOM(極大地提升了效能)

['div',{id:'abc'},['span',{},'happy chen']]

7,比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中的內容 8,直接操作DOM,改變span中的內容

在react中 JSX 到虛擬DOM(js物件)到真實的DOM

虛擬DOM的好處: 1,效能提升了,DOM的比對變成了js物件的比對 2,它使得跨端應用得以實現。React Native,我們可以用react去寫原生的應用。之所以可以用react語法去寫原生應用得益於虛擬DOM的存在,因為渲染DOM在瀏覽器是沒有問題的,但是在移動端的原生應用裡面是不存在DOM這個概念的,生成的DOM在原生應用裡面就無法使用,但是js物件在原生應用裡面是可以被識別的,這個時候虛擬DOM(也就是js物件)就可以生成原生應用裡的元件,這樣原生的應用裡面也可以把頁面展示出來,使得react既可以生成網頁應用也可以生成原生應用。

虛擬DOM中的Diff演算法
react提出了第三種方案,就是虛擬DOM方案:

1,state 資料 2,JSX模板 3,資料 + 模板 結合 ,生成虛擬DOM

['div',{id:'abc'},['span',{},'hello world']]

4,用虛擬DOM的結構,生成真實的DOM,顯示在頁面上

<div id="abc"><span>hello world</span></div>

5,state 發生改變 6,資料 + 模板 結合 ,生成新的虛擬DOM(極大地提升了效能)

['div',{id:'abc'},['span',{},'happy chen']]

7,比較原始虛擬DOM和新的虛擬DOM的區別,找到區別是span中的內容

8,直接操作DOM,改變span中的內容

當資料發生改變時虛擬DOM才會去比對,要麼改變了state,要麼改變了props,其實props的改變也是因為父元件的state發生了改變,所以歸根結底是 setState方法呼叫改變state時資料發生變化,然後虛擬DOM才去比對。 setState是非同步的,非同步是為了提高react底層的效能。比如我連續呼叫了三次setState,變更三組資料,這三次呼叫的時間間隔非常小,如果我們每次都去比對就會消耗效能。所以react會把這三次setState合併成一個setState只去做一次虛擬DOM的比對然後更新DOM,這樣就會省去額外的兩次虛擬DOM的比對。所以setState做成非同步本質上是為了提高react底層的效能。

Diff演算法裡有個很重要的概念是同級比較。 圖上,左側是一個虛擬DOM,當資料發生改變時,右側是新的虛擬DOM,然後兩個虛擬DOM做比對。找到差異之後去更新真實的DOM。在做比對的時候會做同層的比對,首先會比較最頂層的DOM節點,假設它倆一致再去比對第二層。如果不一致,react就不會再繼續往下比對了,它發現第一層DOM是有差異的,會把原始頁面上虛擬DOM對應的所有下面節點的DOM全部都刪除掉重新生成新的第一層節點下的虛擬DOM,然後用重新生成的DOM替換原始的DOM。也就是它發現這層虛擬DOM不一致的話下面就不會再比對了。雖然下面的節點有可能是一樣的,但是這種同層比對的演算法更加簡單,我只需要一層一層的做對比就好了,演算法簡單帶來的好處就是比對的速度會非常的快。 在做虛擬DOM節點迴圈時,我們可以給每個節點取個名字。這個時候虛擬DOM的比對會根據key值做關聯,如上圖,我們就可以很快的發現只增加了一個Z。所以之前虛擬DOM節點名字a,b,c,d,e,到了新的虛擬DOM節點名字還是a,b,c,d,e,,只是改變的那個節點不同。所以我們在寫程式碼的時候key值不要是index,因為這樣你就無法保證原始虛擬DOM的key值和新的虛擬DOM的key值一致了。 總結: setState非同步可以把多次setState結合成一次setState,減少虛擬DOM比對的次數。在虛擬DOM比對的時候會有同層比對的概念,也就是Diff演算法比對兩個虛擬DOM差異的時候它會逐層的比對,如果一層不一致下面就不會再比對了,直接廢棄掉用新的替換老的就可以了,這樣會提高效能。react迴圈中引入key值是為了提高react虛擬DOM比對的效能,所以key值要保持穩定,在專案中能不用index做key值就一定不要用index做key值,因為index值是不穩定的。