【插播一條】虛擬DOM Diff算法
我們都知道React通過(guò)虛擬DOM機(jī)制可以有效解決復(fù)雜的DOM操作帶來(lái)的性能瓶頸,并且每當(dāng)數(shù)據(jù)變化時(shí),React都會(huì)重新構(gòu)建整個(gè)DOM樹,然后將當(dāng)前DOM樹和上次DOM樹進(jìn)行對(duì)比,然后將需要變化的部分進(jìn)行實(shí)際更新。那么,需要變化的部分細(xì)化到什么地步,虛擬DOM是又如何運(yùn)作的?不懂這些對(duì)我們實(shí)際使用react并不會(huì)有什么困擾,但是理解這些對(duì)我們實(shí)現(xiàn)自定義組件時(shí)如何進(jìn)一步優(yōu)化性能具有指導(dǎo)意義。
Diff算法復(fù)雜度為O(n),這基于下面兩個(gè)假設(shè):
- 兩個(gè)相同組件產(chǎn)生類似的DOM結(jié)構(gòu),不同的組件產(chǎn)生不同的DOM結(jié)構(gòu);
- 對(duì)于同一層次的一組子節(jié)點(diǎn),他們可以通過(guò)唯一ID進(jìn)行區(qū)分。
逐層進(jìn)行節(jié)點(diǎn)比較
React按樹結(jié)構(gòu)從上到下依次遍歷,逐層比較。它只會(huì)對(duì)相同顏色框內(nèi)的DOM節(jié)點(diǎn)比較,也就是同一個(gè)父節(jié)點(diǎn)下的所有子節(jié)點(diǎn)。
當(dāng)該位置前后節(jié)點(diǎn)類型不同時(shí)
當(dāng)樹中的同一位置前后輸出了不同類型的節(jié)點(diǎn):直接刪除前面的節(jié)點(diǎn)(不管該節(jié)點(diǎn)是否有子節(jié)點(diǎn)),然后創(chuàng)建并插入新的節(jié)點(diǎn)。同樣的,同一位置前后輸出不同組件時(shí)也是直接銷毀第一個(gè)組件,然后創(chuàng)建新組件并插入。
對(duì)于上圖,Diff算法的操作是
A.destroy();
A=new A();
A.append(new B());
A.append(new C());
D.append(new D());
假設(shè)這些節(jié)點(diǎn)都是組件,那么從生命周期來(lái)理解的話就是:
A will unmount.
A is created.
B is created.
C is created.
B did mount.
C did mount.
A did mount.
D is updated.
Root is updated.
由上面簡(jiǎn)單的例子可見(jiàn),Diff算法在同一位置不論是對(duì)組件還是組件內(nèi)的節(jié)點(diǎn)只要類型不同直接刪除加重建,這樣從上到下遍歷一次搞定。那么對(duì)于我們平日的使用:能用display:none之類的class解決的事,就不要去替換修改DOM結(jié)構(gòu),(這點(diǎn)我們?cè)趈query實(shí)踐中也基本都做到了)。另外對(duì)于節(jié)點(diǎn)列表的操作(一般涉及到增、刪、查、改、排序),要給每個(gè)節(jié)點(diǎn)去設(shè)置唯一的標(biāo)識(shí)(key),?這可以幫助React定位到正確的節(jié)點(diǎn)進(jìn)行比較,從而大幅減少DOM操作次數(shù),提高性能。
Flux應(yīng)用程序架構(gòu)
一個(gè)小的組件寫起來(lái)好像并不太不煩,譬如先前提到的一個(gè)實(shí)現(xiàn)評(píng)論功能的組件。但是內(nèi)容提交后如何在上面內(nèi)容區(qū)域顯示呢,也就是組件之間如何通信?
Flux的解決方案是讓數(shù)據(jù)流變成單向,引入Store、Action、Action Creators和Dispatcher等概念來(lái)管理信息流,完全面向View,并且始終是整體刷新的思路。如下圖所示:
這顯然是一個(gè)鏈?zhǔn)椒磻?yīng),Action觸發(fā)Store的更新,Store的更新又出發(fā)view的重新渲染。得益于React的View每次更新都是整體刷新的思路,我們可以完全不必關(guān)心Store的變化細(xì)節(jié),只需要監(jiān)聽Store的onChange事件,每次變化都觸發(fā)View的re-render。
回到提交評(píng)論的使用場(chǎng)景(使用Flux):
1、兩個(gè)組件:評(píng)論列表和評(píng)論框
2、所有組件綁定Store(存儲(chǔ)了評(píng)論數(shù)據(jù))
3、Action Creator向服務(wù)器發(fā)送請(qǐng)求
4、Store中監(jiān)聽action,更新自身數(shù)據(jù),然后觸發(fā)view的更新。
組件之間需要共享的狀態(tài)放到Store中進(jìn)行維護(hù),Store中的狀態(tài)改變時(shí),就會(huì)發(fā)布o(jì)nChange事件,訂閱了這個(gè)事件的View就會(huì)執(zhí)行重新渲染,通過(guò)這樣一種發(fā)布——訂閱模式就實(shí)現(xiàn)了從Store到View的數(shù)據(jù)綁定。
那么當(dāng)View接收接收用戶交互后(譬如對(duì)評(píng)論點(diǎn)贊),又如何將新狀態(tài)存入Store?
View接收用戶的輸入之后,產(chǎn)生一個(gè)特定的action,然后通過(guò)Dispatcher分發(fā)處理去更新Store,重新渲染view,這樣也就形成了一個(gè)閉環(huán)的單向流動(dòng)。Dispatcher是全局唯一的,也就是所有action的集中處理中心,而每個(gè)action處理函數(shù)都能接收到所有的action,即所有的action能力都相同,只是當(dāng)前在執(zhí)行的任務(wù)可能不同。另外為了使View更加純粹,將Action的創(chuàng)建邏輯也單拿出去放到了Action Creators中。
Flux的標(biāo)準(zhǔn)實(shí)現(xiàn)非常簡(jiǎn)單,因此還衍生出了很多第三方實(shí)現(xiàn),而如今最為火熱的應(yīng)該屬于Redux,它采用了函數(shù)式編程的思想來(lái)維護(hù)整個(gè)應(yīng)用程序的狀態(tài)。