1.開篇
??先說說為什么要寫這篇文章吧:不知從什么時(shí)候開始蓬抄,大家相信前端摩爾定律:“每18個(gè)月鸯匹,前端難度會增加一倍”。我并不完全認(rèn)可這個(gè)數(shù)字的可靠性凡桥,但是這句話的本意我還是非撤羌#肯定的惯裕。
??是的玄叠,前端越來越簡單了欣尼,但也越來越復(fù)雜了---簡單到你可以用一個(gè)Github
的starter
搭建一個(gè)框架祸穷,集成所有的全家桶性穿,涵蓋單元測試和功能測試,包括部署以及發(fā)布雷滚,甚至你開發(fā)時(shí)使用的UI庫都讓你寫不了幾行css
需曾;可又復(fù)雜到如此多的框架和庫層出不窮,你還沒來得及學(xué)會官網(wǎng)的doc呢,就已經(jīng)有新的替代品了呆万,那就更別提靜下心去學(xué)習(xí)其中的源碼或推敲原理了商源,跟不上腳步強(qiáng)行搬磚自然略顯疲憊。
??正是前端飛速的發(fā)展使得前端看似簡單谋减,但若想深入?yún)s實(shí)屬不易牡彻。順便提一句,去年6月底出爹,ES8
已經(jīng)發(fā)布了庄吼,沒錯(cuò),你沒看錯(cuò)严就,是不感覺學(xué)不動(dòng)了(開玩笑了总寻,其實(shí)也沒更新啥,不會再有ES5
->ES6
這種跨度了)梢为。
??所以渐行,我近期覺得使用的框架有些多了,得靜下心來沉淀沉淀---為什么要說寫組件化思想呢铸董?因?yàn)槲矣X得它是伴隨著前端發(fā)展的一個(gè)不可或缺的設(shè)計(jì)思想殊轴,目前幾大流行框架也都非常好的實(shí)現(xiàn)了組件化,比如React
袒炉,Vue
旁理。React
之前用得算是比較多了,所以本篇我決定以Vue
作為基礎(chǔ)我磁,去談一談前端模塊化孽文,組件化,可維護(hù)化的設(shè)計(jì)細(xì)想夺艰。
2.什么是組件化
??組件化并不是前端所特有的芋哭,一些其他的語言或者桌面程序等,都具有組件化的先例郁副。確切的說减牺,只要有UI層的展示,就必定有可以組件化的地方存谎。簡單來說拔疚,組件就是將一段UI樣式和其對應(yīng)的功能作為獨(dú)立的整體去看待,無論這個(gè)整體放在哪里去使用既荚,它都具有一樣的功能和樣式稚失,從而實(shí)現(xiàn)復(fù)用,這種整體化的細(xì)想就是組件化恰聘。不難看出句各,組件化設(shè)計(jì)就是為了增加復(fù)用性吸占,靈活性,提高系統(tǒng)設(shè)計(jì)凿宾,從而提高開發(fā)效率矾屯。
3.組件化的演變
??如果你對JS的理解還停留在jQuery
的話(jQuery
本身是一個(gè)非常優(yōu)秀的庫),那么請?zhí)^此文(開個(gè)玩笑)初厚。在那個(gè)時(shí)候件蚕,大部分的前端開發(fā)應(yīng)該都是十分過程式的開發(fā):操作DOM
,發(fā)起ajax
請求惧所,刷新數(shù)據(jù)骤坐,局部更新頁面。這樣的動(dòng)作反反復(fù)復(fù)下愈,甚至在同一個(gè)項(xiàng)目里同樣的流程也許還要重復(fù)纽绍,其實(shí)jQuery
本身也有有自己模塊化的設(shè)計(jì),有時(shí)我們也會用到類似jQuery UI
等不錯(cuò)的庫來減少工作量势似,但請注意拌夏,這里我只認(rèn)為它是模塊化的。
??頻繁操作DOM履因,過程式的開發(fā)方式的確不怎么樣障簿。這時(shí)開始流行MV*
,比如MVC
栅迄,前端開始學(xué)習(xí)后端的思想站故,講業(yè)務(wù)邏輯,UI毅舆,功能西篓,可以按照不同的文件去劃分,結(jié)構(gòu)清晰憋活,設(shè)計(jì)明了岂津,開發(fā)起來也不錯(cuò)。在這個(gè)基礎(chǔ)上悦即,又有了更加不錯(cuò)的MVVM
框架吮成,它的出現(xiàn),更加簡化了前端的操作辜梳,并且將前端的UI賦予了真實(shí)意義:你所看到的任何UI粱甫,應(yīng)該都對應(yīng)其相應(yīng)的ViewModel
,即你看到的view
就是真實(shí)的數(shù)據(jù)冗美,并且實(shí)現(xiàn)了雙向綁定魔种,只要UI改變,UI所對應(yīng)的數(shù)據(jù)也改變粉洼,反之亦然节预。這的確很方便,但大部分的MVVM
框架属韧,并沒有實(shí)現(xiàn)組件化安拟,或者說沒有很好的實(shí)現(xiàn)組件化,因?yàn)?code>MVVM最大的問題就是:
1.執(zhí)行效率宵喂,只要數(shù)據(jù)改變糠赦,它下面所有監(jiān)測數(shù)據(jù)上綁定的UI一般都會去更新,效率很低锅棕,如果你操作頻繁拙泽,很可能調(diào)了幾十萬遍(有可能層次太深或者監(jiān)測了太多的數(shù)據(jù)變化)。
2.由于
MVVM
一般需要嚴(yán)格的ViewModel
的作用域裸燎,因此大部分情況不支持多次綁定顾瞻,或者只允許綁定一個(gè)根節(jié)點(diǎn)做為頂層DOM渲染,這就給組件化帶來了困難(不能獨(dú)立的去綁定部分UI)德绿。
??而后荷荤,在此基礎(chǔ)上,一些新的前端框架“取其精華移稳,去其糟粕”蕴纳,開始大力推廣前端組件化的開發(fā)方式,從這一點(diǎn)來說个粱,React
和Vue
是類似的古毛。
??但從框架本身來說,React
和Vue
是完全不同的都许,前者是單向數(shù)據(jù)流管理設(shè)計(jì)的先驅(qū)稻薇,如果非讓我做一個(gè)不恰當(dāng)?shù)谋容^的話,我覺得React+Redux
是將MVC
做到了極致(action->request, reducer->controller
)梭稚;而后者則是后起之秀颖低,既吸取了React
的數(shù)據(jù)流管理方式(Vue
本身也可以用類似React
的方式去開發(fā),但難度比較大而已弧烤,不是很Vue
)的設(shè)計(jì)理念忱屑,也實(shí)現(xiàn)了MVVM
的雙向綁定和數(shù)據(jù)監(jiān)控(這應(yīng)該是Vue
的核心了),所以Vue
是比較靈活的暇昂,可以按需擴(kuò)展莺戒,它才敢稱自己是漸進(jìn)式框架。
PS1: 并非討論孰好孰壞急波,兩大框架我都很喜歡从铲,因?yàn)槎挤浅:玫膶?shí)現(xiàn)了組件化。
PS2: 上面有提到模塊化澄暮,個(gè)人覺得如果更廣義的來講名段,模塊化和組件化并不在一個(gè)維度上阱扬,模塊化往往是代碼的設(shè)計(jì)和項(xiàng)目結(jié)構(gòu)的設(shè)計(jì);但很多時(shí)候在狹義的場景中伸辟,比如一個(gè)很通用的功能麻惶,也完全能夠?qū)⑵浣M件化或模塊化,這兩者此時(shí)十分相似信夫,最大的區(qū)別就是組件必定是模塊化的窃蹋,并且往往需要實(shí)例化,也應(yīng)當(dāng)賦有生命周期静稻,而模塊化往往是直接引用警没。
4.如何實(shí)現(xiàn)組件化
??我就以搜房網(wǎng)為例(最近房價(jià)居高不下,各個(gè)大佬還在吹各種牛x說房價(jià)不久后將白菜價(jià)振湾,我順便mark下看以后打誰的臉)進(jìn)行demo分析杀迹。隨手截圖如下:
4.1分析頁面布局
??從大體上來看,可以分為頂部搜索恰梢,中間內(nèi)容展示佛南。而中間內(nèi)容又分為part1,2嵌言,3三種類型嗅回。由于篇幅問題,本文只分析part1摧茴,2绵载,3
??每一個(gè)part中又可以分為header(title + link)和content(每個(gè)part不一樣)
4.2初步開發(fā)
如果沒有經(jīng)過任何設(shè)計(jì),也許會出現(xiàn)下面的代碼:
<template>
<div id="app">
<div class="nav-search">...</div>
<div class="panel">
<div class="part1 left">
<div>
<span>萬科城潤園樓盤動(dòng)態(tài)</span>
<a href="">更多動(dòng)態(tài)>></a>
</div>
<div>這里是每個(gè)part里面的具體內(nèi)容</div>
</div>
<div class="part2 right">
<div>
<span>樓盤故事</span>
<a href="">更多>></a>
</div>
<div>這里是每個(gè)part里面的具體內(nèi)容</div>
</div>
<div class="part3">
<div>
<span>萬科城潤園戶型</span>
<a href="">二居(1)</a>
<a href="">三居(4)</a>
<a href="">四居(3)</a>
<a href="">更多>></a>
</div>
<div>這里是每個(gè)part里面的具體內(nèi)容</div>
</div>
</div>
</div>
</template>
其中我省略了大部分的細(xì)節(jié)實(shí)現(xiàn)苛白,實(shí)際代碼量應(yīng)該是這里的數(shù)倍娃豹。
這段代碼有幾個(gè)問題:
1.part1,2购裙,3的結(jié)構(gòu)很類似懂版,有些許重復(fù)
2.實(shí)際的代碼量將會很多,很難快速定位問題躏率,維護(hù)難度較大
4.3化繁為簡
首先我們可以將part1躯畴,2,3進(jìn)行分離薇芝,這樣就獨(dú)立出來三個(gè)文件蓬抄,那么結(jié)構(gòu)上將會非常清晰
<template>
<div id="app">
<div class="nav-search">...</div>
<div class="panel">
<part1 />
<part2 />
<part3 />
</div>
</template>
這有些類似將一個(gè)大函數(shù)逐步拆解成幾部分的過程,不難想象part1夯到,2嚷缭,3中的代碼,必然是適用性很差,確切的說只有這里能夠引用阅爽。(但我看過很多項(xiàng)目的代碼路幸,就是這么干的,認(rèn)為自己做了組件化优床,抽象還不錯(cuò)(@_@)
)
4.4組件抽象
仔細(xì)觀察part1劝赔,2誓焦,3胆敞,正如我上面所說,它們其實(shí)是很相似的:都具有相同的外層border并附有shadow杂伟,都具有抬頭和顯示更多移层,各自內(nèi)容部分暫不細(xì)說的話,這三個(gè)完全就是一模一樣赫粥。
如此观话,我們將具有高度相似的業(yè)務(wù)數(shù)據(jù)進(jìn)行抽離,實(shí)現(xiàn)組件的抽象越平。
part.vue
<template>
<div class="part">
<div class="hearder">
<span>{{ title }}</span>
<a :href="linkForMore">{{ showMore || '更多>>' }}</a>
</div>
<slot name="content" />
</div>
</template>
我們將part內(nèi)可以抽象的數(shù)據(jù)都做成了props频蛔,包括利用slot去做模版,同時(shí)showMore || '更多>>'
也考慮到了part1的link名字和其他幾個(gè)part不一致的情況秦叛。
這樣一來app.vue就更加清晰化
<template>
<div id="app">
<div class="nav-search">...</div>
<div class="panel">
<part
title="萬科城潤園樓盤動(dòng)態(tài)"
linkForMore="#1"
showMore="更多動(dòng)態(tài)>>"
>
<div slot="content">這里是part1里面的具體內(nèi)容</div>
</part>
<part
title="樓盤故事"
linkForMore="#2"
>
<div slot="content">這里是part2里面的具體內(nèi)容</div>
</part>
<part
title="萬科城潤園戶型"
linkForMore="#3"
>
<div slot="content">這里是part3里面的具體內(nèi)容</div>
</part>
</div>
</template>
這里有幾點(diǎn)需要說明一下:
- 1.三個(gè)part中部分UI差異應(yīng)該在哪里定義晦溪?
比如三個(gè)part的寬度都不一樣,并且part1和part2可能要需要進(jìn)行浮動(dòng)挣跋。
必須要記住三圆,這種差異并不是組件本身的,<part />
的設(shè)計(jì)本身應(yīng)該是無浮動(dòng)并且寬度占100%的避咆,至于占誰的100%舟肉,那就取決于誰引用它,至于向左還是向右浮動(dòng)查库,同樣也取決于引用它的container需要自己去定義路媚,在上面的代碼中,app.vue
就應(yīng)該是<part />
的container樊销,app想要的是一個(gè)左浮動(dòng)且寬度為80%的part(part1)整慎,右浮動(dòng)且寬度為20%的part(part2)和一個(gè)寬度為100%的part(part3),但它們都是part现柠,所以應(yīng)該由app來設(shè)置這些差異院领。
記住這一點(diǎn),將給你的抽象和擴(kuò)展但來事半功倍的效果够吩。
- 2.三個(gè)part中的數(shù)據(jù)差異應(yīng)該在哪里定義比然?
比如part3中,其他的part只有一個(gè)類似更多>>
的link周循,但是它卻有多個(gè)(一居强法,二居...
)万俗。
這里我推薦將這種差異體現(xiàn)在組件內(nèi)部,設(shè)計(jì)方法也很多:
比如可以將link數(shù)組化為links饮怯;
比如可以將更多>>
看作是一個(gè)default的link闰歪,而多余的部分則是用戶自定義的特殊link,這兩者合并組成了links蓖墅。用戶自定義的默認(rèn)是沒有的库倘,需要引用組件時(shí)進(jìn)行傳入。
總之论矾,只要有數(shù)據(jù)差異化教翩,就應(yīng)該結(jié)合組件本身和業(yè)務(wù)上下文將差異合理的消除在內(nèi)部。
- 3.注意組件內(nèi)數(shù)據(jù)的命名方式
一個(gè)通用的贪壳,可擴(kuò)展性高的組件饱亿,必然是有非常合理的命名的,比如觀察一些組件庫的命名闰靴,總會出現(xiàn)類似list,data,content,name,key,callback,className
等名詞彪笼,絕對不會出現(xiàn)我們系統(tǒng)中的類似iterationList, projectName
等業(yè)務(wù)名詞,這些名詞和任一產(chǎn)品和應(yīng)用都無關(guān)蚂且,它與自身抽象的組件有關(guān)配猫,只表明組件內(nèi)部的數(shù)據(jù)含義,偶爾也會代表其結(jié)構(gòu)膘掰,所以只有這樣章姓,才能讓用戶通用。
我們在組件化時(shí)识埋,也需要遵循這種設(shè)計(jì)原則凡伊,但庫往往是想讓廣大開發(fā)者通用,而我們可以降低scope窒舟,做到在整個(gè)app內(nèi)通用即可系忙。所以從這個(gè)角度來說,好的組件化必然有好的BA和UX惠豺,這是大實(shí)話
5.寫在最后
你也許會認(rèn)為這樣抽象沒有太大的必要性银还,畢竟它只是一段靜態(tài)UI(pure component
),但任何的設(shè)計(jì)都是基于一定的復(fù)雜度才衍生出來的洁墙,其實(shí)大部分情況下這種設(shè)計(jì)都是需要將功能邏輯代碼也納入其中的蛹疯,并不光只是UI(如antd, element-ui等),我這里舉的例子也相對比較簡單热监,并不想有太多的代碼捺弦。
個(gè)人認(rèn)為在一個(gè)大型前端項(xiàng)目中,這種組件化的抽象設(shè)計(jì)是很重要的,不僅增加了復(fù)用性提高了工作效率列吼,從某種程度上來說也反應(yīng)了程序員對業(yè)務(wù)和產(chǎn)品設(shè)計(jì)的理解幽崩,一旦有問題或者需要功能擴(kuò)展時(shí),你就會發(fā)現(xiàn)之前的設(shè)計(jì)是多么的make sense
(畢竟需求總是在變哪)寞钥。