基于snabbdom和jsx優(yōu)化開(kāi)發(fā)的分析與實(shí)踐

一髓堪、背景介紹

目前前端項(xiàng)目開(kāi)發(fā)普遍會(huì)基于react踩娘、vue等框架,采用數(shù)據(jù)驅(qū)動(dòng)的模式钞护,通過(guò)虛擬dom減少與真是dom的交互來(lái)提高渲染性能盖喷。使用react、vue等框架開(kāi)發(fā)就需要按照它們的要求定義組件难咕、調(diào)用組件课梳、傳遞數(shù)據(jù),轉(zhuǎn)變之前的開(kāi)發(fā)思路步藕,這些框架非常完善惦界,但也具有很高約束性。對(duì)于一些沒(méi)有使用react咙冗、vue等框架開(kāi)發(fā)的項(xiàng)目沾歪,若它們自身也有比較完善的組件定義、組件調(diào)用雾消、數(shù)據(jù)傳遞的方式灾搏,且框架基本上也能滿足使用。對(duì)于這樣的項(xiàng)目若想采用數(shù)據(jù)驅(qū)動(dòng)和虛擬dom渲染立润,是將原有框架全部否定狂窑,花很多的時(shí)間使用react、vue等框進(jìn)行重構(gòu)桑腮?其實(shí)只需把存在用戶交互的組件調(diào)整為數(shù)據(jù)驅(qū)動(dòng)和虛擬dom渲染即可泉哈。那選擇混合react、vue等框架只重構(gòu)部分UI組件?這樣會(huì)導(dǎo)致在項(xiàng)目中維護(hù)兩種不同的組件定義丛晦、調(diào)用方式奕纫。本文將介紹如何基于snabbDom.jsjsx,在不改變?cè)蚣艿慕M件定義烫沙、組件調(diào)用匹层、數(shù)據(jù)傳遞的前提下,靈活的加入數(shù)據(jù)驅(qū)動(dòng)的思想锌蓄,并通過(guò)虛擬dom和diff算法簡(jiǎn)化dom操作優(yōu)化組件的渲染機(jī)制升筏。

二、snabbdom的使用

snabbdom是一個(gè)輕量級(jí)的虛擬dom框架瘸爽,具有高效的diff算法及靈活的擴(kuò)展性您访,vue就融合了snabbDom并在它的基礎(chǔ)上進(jìn)行了優(yōu)化與擴(kuò)展。

1蝶糯、創(chuàng)建Vnode

snabbDom的h方法用于創(chuàng)建Vnode對(duì)象洋只,Vnode的屬性可以用來(lái)描述真實(shí)的dom節(jié)點(diǎn),而虛擬dom就是一棵以 Vnode (virtual node)對(duì)象為基礎(chǔ)的樹(shù)昼捍。h方法接受三個(gè)參數(shù):元素標(biāo)簽 / 選擇器识虚、屬性對(duì)象、子節(jié)點(diǎn)數(shù)組:

由h方法創(chuàng)建的Vnode對(duì)象包含的屬性有:sel元素選擇器妒茬、data屬性對(duì)象担锤、children子節(jié)點(diǎn)數(shù)組、text文本內(nèi)容乍钻、elm真實(shí)dom的指針肛循、key標(biāo)識(shí),具體格式如下:

2银择、對(duì)比Vnode

Vnode是真實(shí)dom在內(nèi)存中的描述多糠,要實(shí)現(xiàn)數(shù)據(jù)改變時(shí)視圖更新,需要使用diff算法對(duì)新浩考、老Vnode進(jìn)行對(duì)比夹孔,將變動(dòng)映射到真實(shí)dom上才行。目前主流的diff算法思路上大體一致析孽,都是在深度優(yōu)先遍歷DFS算法基礎(chǔ)上搭伤,結(jié)合了真實(shí)dom的特性,在對(duì)比虛擬dom樹(shù)時(shí)增加了同級(jí)對(duì)比袜瞬、同類別對(duì)比怜俐、列表元素結(jié)合key標(biāo)識(shí)進(jìn)行對(duì)比的優(yōu)化,復(fù)雜度為O(n)具有很高的性能邓尤,而snabbdom在此基礎(chǔ)上拍鲤,又增加了列表元素的兩端對(duì)比贴谎,對(duì)比算法更為高效。

snabbdom的diff是通過(guò)patch方法來(lái)實(shí)現(xiàn)的殿漠,patch方法可以對(duì)比新赴精、老Vnode,把變動(dòng)的Vnode映射到真實(shí)dom上绞幌,也可以把Vnode掛載到html上,并替換指定的dom節(jié)點(diǎn)一忱。patch方法的調(diào)用方式如下:

3莲蜘、兼容ie低版本

使用snabbdom的項(xiàng)目代碼,想要在ie瀏覽器低版本中正常運(yùn)行帘营,需要額外做一些處理票渠,本文只介紹部分:

(1)在snabbdom的樣式處理模塊中,使用了dom元素的classList屬性芬迄,可以將classList的調(diào)用更改為className的字符串處理问顷。

(2)在snabbdom的自定義數(shù)據(jù)屬性處理模塊中,使用了dataset禀梳,可以更改為調(diào)用setAttribute和removeAttribute杜窄。

(3)在snabbdom的事件處理模塊中,綁定事件時(shí)使用了addEventListener算途,ie9以下需要使用attachEvent綁定事件塞耕。

三、jsx

在代碼中直接調(diào)用snabbdom的h方法來(lái)描述dom結(jié)構(gòu)嘴瓤,需要多層嵌套調(diào)用可讀性差不好維護(hù)扫外,我們可以像react一樣,在代碼中使用jsx描述dom結(jié)構(gòu)廓脆,然后使用轉(zhuǎn)譯工具將jsx轉(zhuǎn)換成h方法的調(diào)用筛谚。jsx是一個(gè)看起來(lái)很像XML的JavaScript語(yǔ)法擴(kuò)展,使用jsx表示dom的結(jié)構(gòu)與層次就像使用html的標(biāo)簽語(yǔ)法那樣清晰停忿,jsx的出現(xiàn)讓“ALL-in-JS”的思想得到進(jìn)一步實(shí)現(xiàn)驾讲。

1、jsx的轉(zhuǎn)換原理

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jsx轉(zhuǎn)換為h方法調(diào)用的過(guò)程

編譯工具(如:Babel瞎嬉、JSTransform)先將jsx代碼解析轉(zhuǎn)換為AST(抽象語(yǔ)法樹(shù))蝎毡,在通過(guò)轉(zhuǎn)換插件(如plugin-transform-react-jsxjsx-transform)對(duì)AST進(jìn)行遍歷氧枣,查找類型為JSXElement或者JSXFragment的節(jié)點(diǎn)沐兵,

轉(zhuǎn)換插件會(huì)將該類型節(jié)點(diǎn)替換為h方法對(duì)應(yīng)的內(nèi)容,等AST中所有的JSXElement或者JSXFragment節(jié)點(diǎn)轉(zhuǎn)換完成后便监,轉(zhuǎn)換插件會(huì)將處理后的AST編譯為js代碼輸出扎谎,此時(shí)jsx代碼就被轉(zhuǎn)換為js代碼中的h方法的調(diào)用了:

2蜕该、jsx的轉(zhuǎn)換配置

介紹兩種jsx轉(zhuǎn)換為h方法調(diào)用的方式及配置:

(1)在打包代碼之前,通過(guò)node腳本調(diào)用jsx-transform將jsx轉(zhuǎn)換為js代碼督暂,然后在打包轉(zhuǎn)換后的js蛮位。而jsx-transform是一個(gè)將jsx脫離于react,可以被使用于其他非react模塊中的可配置轉(zhuǎn)換工具预吆,jsx-transform的factory屬性可以用于設(shè)置要轉(zhuǎn)換成的方法名龙填,具體node腳本實(shí)現(xiàn)如下:

(2)使用webpack進(jìn)行打包構(gòu)建的項(xiàng)目,可以在webapck配置文件中設(shè)置Babel的plugin-transform-react-jsx插件拐叉,將pragma選項(xiàng)設(shè)置為你想要的函數(shù)名岩遗,即可實(shí)現(xiàn)jsx的轉(zhuǎn)換。

3凤瘦、不同的轉(zhuǎn)換插件帶來(lái)的差異

不同轉(zhuǎn)換插件轉(zhuǎn)換jsx代碼時(shí)宿礁,jsx的書(shū)寫格式會(huì)有一些差異,比如:使用plugin-transform-react-jsx和jsx-transform蔬芥,使用jsx描述嵌套多個(gè)子元素時(shí)梆靖,jsx的書(shū)寫格式就不太一樣。


使用不同的轉(zhuǎn)換插件除了jsx的書(shū)寫格式有所差異外笔诵,轉(zhuǎn)換為h方法的調(diào)用時(shí)返吻,傳參類型可能會(huì)與形參類型不同,比如列表類型的子元素使用jsx-transform轉(zhuǎn)換時(shí)嗤放,傳遞給h方法的children參數(shù)會(huì)被轉(zhuǎn)換為二維數(shù)組思喊,此時(shí)就需要增加兼容處理,讓h方法兼容children為二維數(shù)組的情況次酌。

在文中介紹的兩種轉(zhuǎn)換方式中恨课,jsx的書(shū)寫格式和轉(zhuǎn)換后的方法參數(shù)格式都細(xì)微需要兼容處理的部分。如果想要按照自己的風(fēng)格開(kāi)發(fā)jsx岳服,并讓jsx轉(zhuǎn)換成你想要的方法調(diào)用和傳參格式剂公,可以自己開(kāi)發(fā)一個(gè)轉(zhuǎn)換插件,插件的開(kāi)發(fā)可以參考Babel pluginsjsx PrimaryExpression吊宋。

4纲辽、支持自定義組件的jsx寫法?

snabbdom目前只支持將html的標(biāo)簽元素如:div璃搜、span拖吼、svg等標(biāo)簽用Vnode進(jìn)行描述并更新到頁(yè)面,如果想像react中那樣能夠使用jsx語(yǔ)法表示自定義組件和組件的嵌套關(guān)系这吻。自定義組件的jsx寫法:

為了保證統(tǒng)一的定義吊档、調(diào)用方式,組件的整體結(jié)構(gòu)設(shè)計(jì)沒(méi)有改動(dòng)唾糯,只是將組件中對(duì)dom結(jié)構(gòu)的描述方式做了調(diào)整怠硼,由原本的js字符串拼接html標(biāo)簽調(diào)整為jsx語(yǔ)法來(lái)表示鬼贱,snabbdom中的基本標(biāo)簽類型就能夠滿足我們的需求,所以本文目前沒(méi)有支持自定義組件的jsx寫法香璃。若想實(shí)現(xiàn)這部分功能可以在h方法的基礎(chǔ)上進(jìn)行擴(kuò)展这难,使snabbdom的h方法能夠處理sel為自定義組件類型的情況,比如增加自定義組件類型的判斷葡秒、自定義組件Vnode格式的定義與創(chuàng)建姻乓、自定義組實(shí)例的創(chuàng)建、描述組件間嵌套關(guān)系的數(shù)據(jù)結(jié)構(gòu)的處理等等眯牧,實(shí)現(xiàn)思路可以參考reactjs源碼分析這篇文章糖权。

四、在業(yè)務(wù)中的應(yīng)用與實(shí)踐

1炸站、原項(xiàng)目框架特點(diǎn)

項(xiàng)目采用了MVC的開(kāi)發(fā)模式,其中Model為一個(gè)配置文件疚顷,設(shè)置每個(gè)組件的狀態(tài)數(shù)據(jù)旱易,比如組件類型、默認(rèn)顯示隱藏腿堤、正則規(guī)則阀坏,并控制組件的嵌套關(guān)系。View對(duì)應(yīng)的就是UI組件部分笆檀。Controller控制模塊負(fù)責(zé)循環(huán)遍歷配置文件忌堂,根據(jù)配置文件中的組件狀態(tài)數(shù)據(jù)、嵌套關(guān)系酗洒,創(chuàng)建組件實(shí)例士修,控制組件的渲染,對(duì)內(nèi)存中組件對(duì)象進(jìn)行維護(hù)管理樱衷。除此外還有base組件棋嘲、車輛信息校驗(yàn)?zāi)K,其中UI組件都繼承自base組件矩桂。項(xiàng)目框架如下:

2沸移、為引入數(shù)據(jù)驅(qū)動(dòng)和虛擬dom所做的調(diào)整

只有UI組件部分才存在用戶交互,所以我們只給UI組件增加數(shù)據(jù)驅(qū)動(dòng)和虛擬dom侄榴,其他層級(jí)的組件不進(jìn)行改動(dòng)雹锣,降低改動(dòng)成本。而且我們并不打算一次性把二手車發(fā)布pc端項(xiàng)目中所有的UI組件進(jìn)行優(yōu)化癞蚕,而是隨著業(yè)務(wù)需求將涉及到的組件進(jìn)行調(diào)整優(yōu)化蕊爵。

UI組件繼承了base組件,統(tǒng)一定義了用于存儲(chǔ)狀態(tài)數(shù)據(jù)的opts對(duì)象涣达、拼接dom字符串createElem方法在辆、將jq對(duì)象渲染到頁(yè)面上的render方法证薇。

為支持虛擬dom需要做如下的調(diào)整:

(1)將UI組件的createElem方法調(diào)整為使用jsx描述dom結(jié)構(gòu),初始化渲染時(shí)調(diào)用createElem創(chuàng)建組件的Vnode匆篓。且將原本用于存儲(chǔ)組件jquery對(duì)象的container屬性浑度,調(diào)整為存儲(chǔ)組件的Vnode對(duì)象,在數(shù)據(jù)更新diff新老Vnode時(shí)作為老的Vnode鸦概。

(2)新增JSXComnent組件箩张,它繼承了base組件來(lái)保持與不改動(dòng)組件的統(tǒng)一性,同時(shí)所有為了使用虛擬dom而增加的特有方法都在JSXComnent中窗市,以免影響base組件和不改動(dòng)的UI組件先慷。使用虛擬dom的UI組件需繼承JSXComnent組件來(lái)使用特有的方法,這樣既保留了共性又可以靈活的增加使用虛擬dom時(shí)的特性咨察。

(3)在JSXComnent組件中重寫base的render方法论熙,未改動(dòng)的組件仍繼承base,且container值仍為jq對(duì)象且渲染邏輯不變摄狱。而支持虛擬dom組件的container為Vnode脓诡,重寫后的render方法邏輯調(diào)整為調(diào)用snabbdom的patch方法將Vnode掛載到頁(yè)面中。

(4)在JSXComnent組件增加setOpts方法媒役,當(dāng)數(shù)據(jù)改變時(shí)調(diào)用setOpts方法更新opts對(duì)象祝谚,并調(diào)用createElem方法根據(jù)新的opts創(chuàng)建新的Vnode,在調(diào)用patch方法進(jìn)行新老Vnode對(duì)比并將變動(dòng)映射到真實(shí)的dom上酣衷。為了避免連續(xù)多次調(diào)setOpts會(huì)頻繁的生成Vnode和渲染dom交惯,需要將多次數(shù)據(jù)更新進(jìn)行合并批量處理,目前這部分處理還不夠完善穿仪,之后會(huì)參考react狀態(tài)合并和多任務(wù)方案進(jìn)行優(yōu)化席爽。最終的渲染流程如下:

3、結(jié)合了snabbdom和jsx的組件定義與使用

(1)使用虛擬dom組件的定義:

(2)組件的調(diào)用保持不變牡借,通過(guò)new創(chuàng)建對(duì)象實(shí)例并初始化組件的opts屬性拳昌。以車身顏色為例:

(3)組件的嵌套保持不變,仍通過(guò)配置文件控制父子組件的嵌套關(guān)系钠龙,然后通過(guò)controller組件循環(huán)遍歷初始化組件炬藤。

五、總結(jié)

本文結(jié)合項(xiàng)目的特點(diǎn)基于snabbdom和jsx在項(xiàng)目中增加數(shù)據(jù)驅(qū)動(dòng)和虛擬dom的思想來(lái)優(yōu)化組件的開(kāi)發(fā)碴里,將我們從頻繁的dom操作中解脫出來(lái)沈矿。由于snabbdom自身的靈活性,雖然可以便捷的接入到項(xiàng)目中咬腋,但接入時(shí)需要考慮組件的設(shè)計(jì)羹膳、對(duì)象數(shù)據(jù)結(jié)構(gòu)等因素來(lái)制定適合自身項(xiàng)目的方案來(lái)達(dá)到靈活、快速接入的目的根竿,若投入較大的接入和維護(hù)成本那就不如直接使用react陵像、vue的框架進(jìn)行開(kāi)發(fā)了就珠。希望通過(guò)本文能為大家提供一種優(yōu)化組件開(kāi)發(fā)的參考方案。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末醒颖,一起剝皮案震驚了整個(gè)濱河市妻怎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泞歉,老刑警劉巖逼侦,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異腰耙,居然都是意外死亡榛丢,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門挺庞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)晰赞,“玉大人,你說(shuō)我怎么就攤上這事选侨”龇危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵侵俗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我丰刊,道長(zhǎng)隘谣,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任啄巧,我火速辦了婚禮寻歧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秩仆。我一直安慰自己码泛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布澄耍。 她就那樣靜靜地躺著噪珊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪齐莲。 梳的紋絲不亂的頭發(fā)上痢站,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音选酗,去河邊找鬼阵难。 笑死,一個(gè)胖子當(dāng)著我的面吹牛芒填,可吹牛的內(nèi)容都是我干的呜叫。 我是一名探鬼主播空繁,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朱庆!你這毒婦竟也來(lái)了盛泡?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤椎工,失蹤者是張志新(化名)和其女友劉穎饭于,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體维蒙,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掰吕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颅痊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片殖熟。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖斑响,靈堂內(nèi)的尸體忽然破棺而出菱属,到底是詐尸還是另有隱情,我是刑警寧澤舰罚,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布纽门,位于F島的核電站,受9級(jí)特大地震影響营罢,放射性物質(zhì)發(fā)生泄漏赏陵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一饲漾、第九天 我趴在偏房一處隱蔽的房頂上張望蝙搔。 院中可真熱鬧,春花似錦考传、人聲如沸吃型。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)勤晚。三九已至,卻和暖如春泉褐,著一層夾襖步出監(jiān)牢的瞬間运翼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工兴枯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留血淌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像悠夯,于是被迫代替她去往敵國(guó)和親癌淮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349