vue有著完整的組件化開(kāi)發(fā)機(jī)制,但是官網(wǎng)只給了開(kāi)發(fā)的方式,對(duì)于開(kāi)發(fā)規(guī)范以及組件化開(kāi)發(fā)的最佳實(shí)踐,還需要我們來(lái)摸索。本文就平時(shí)開(kāi)發(fā)中的經(jīng)驗(yàn)來(lái)談?wù)劇鞍盐者吔纭焙汀盃顟B(tài)驅(qū)動(dòng)”這兩個(gè)話題。

邊界把握

邊界把握其實(shí)很好理解。在模塊化編程中,我們通常要定義好一個(gè)模塊的功能邊界,做什么,不做什么,從外部接收什么,向外部提供什么。在vue的組件化系統(tǒng)之下,這些問(wèn)題又更具體一些,需要我們細(xì)細(xì)把握。

劃分業(yè)務(wù)邏輯

這個(gè)原則適用于任何模塊化開(kāi)發(fā),一個(gè)組件要負(fù)責(zé)哪些業(yè)務(wù),在開(kāi)始寫之初就應(yīng)該非常明確,否則邊界就容易模糊了。舉個(gè)例子,頁(yè)面上有個(gè)彈出層,里面會(huì)顯示用戶名。那么在彈出層組件中,需要有username這樣一個(gè)數(shù)據(jù)嗎?

很顯然是不需要的。彈出層的任務(wù)就是:彈出、關(guān)閉、顯示內(nèi)容。至于是什么內(nèi)容,組件并不需要關(guān)心。所以我們頂多會(huì)定義一個(gè)通用的content字段,或者干脆用slot。

組件簡(jiǎn)單了尚且容易把握,當(dāng)業(yè)務(wù)較復(fù)雜的時(shí)候就需要好好斟酌了,這是個(gè)基本思維。

父子通信的注意點(diǎn)

這個(gè)話題想必大家不陌生,你甚至可以朗朗上口的背出來(lái):父通過(guò)props傳遞數(shù)據(jù)給子,子通過(guò)emit發(fā)送消息給父。這有什么好說(shuō)的呢?

props容易忽略的問(wèn)題在于,當(dāng)父組件傳遞一個(gè)對(duì)象給子組件時(shí),這個(gè)傳遞就不再是“單向”的。因?yàn)樽咏M件拿到的是一個(gè)引用,當(dāng)子組件修改了該對(duì)象上的屬性值,父組件的數(shù)據(jù)也會(huì)相應(yīng)變化。數(shù)據(jù)流就變成了雙向的,子組件是不應(yīng)該直接修改父組件的數(shù)據(jù)的。所以我們要在props中只傳遞簡(jiǎn)單值。對(duì)象、數(shù)組這樣的引用類型要避免傳遞。

為了保證props傳遞的數(shù)據(jù)類型,推薦在定義props的時(shí)候?qū)懨黝愋秃湍J(rèn)值:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

props: {
    name: {
        type: string,        default: ''
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

關(guān)于子組件emit消息,我之前也談到過(guò)一個(gè)原則,子組件需要對(duì)外通知的是“我發(fā)生了什么”,而不是“你去干什么”。這只是語(yǔ)義上的一個(gè)差別,往小里說(shuō)只是一個(gè)命名的事。但從邏輯上來(lái)講,缺是一個(gè)邊界把握不清楚的行為。

這也是很容易想通的,如果讓子組件決定父組件的行為,那么他們?cè)谶壿嬌媳泷詈狭?。舉個(gè)例子:點(diǎn)擊彈出層上的確定按鈕,父組件去請(qǐng)求商品列表。那么子組件發(fā)出的消息應(yīng)該叫"confirm"或"ok",而不是叫"request-product"。

避免全局操作

我們?cè)谄綍r(shí)的編程中,通常會(huì)用一些BOM的方法如history,或者是使用document上的方法,這類訪問(wèn)全局對(duì)象的行為,我也視之為“越界”行為。畢竟已經(jīng)跨出了組件之外了。

一旦一個(gè)組件有操作全局對(duì)象的行為,那它就可以被認(rèn)為有潛在威脅。所以通常應(yīng)該注意以下方面:

  1. 用this.$el.querySelector代替document.querySelector,不要去查詢組件外的DOM

  2. 用到的BOM接口,統(tǒng)一封裝成模塊,在組件中引入使用

  3. 本地存儲(chǔ)也進(jìn)行一次包裝,例如,把localStorage相關(guān)操作統(tǒng)一封到一個(gè)storage.js模塊中

  4. 子組件盡量避免監(jiān)聽(tīng)window的事件,可讓最外層組件監(jiān)聽(tīng),然后傳遞數(shù)據(jù)

vuex的狀態(tài)管理

如果你使用了vuex,那么store中的數(shù)據(jù)管理也是需要留意的。vue完美集成了vuex這樣一個(gè)全局狀態(tài)管理工具,可以在任何組件中通過(guò)this.store訪問(wèn)/提交狀態(tài)。

既然是全局狀態(tài),我們擔(dān)心的又來(lái)了,組件內(nèi)操作全局的東西,豈不是一次越界行為?而且各種commit散落在各個(gè)組件中,將來(lái)找起來(lái)豈不是很麻煩?

我的做法是這樣的,單獨(dú)定義一個(gè)模塊,姑且叫做storeMonitor吧,所有修改全局狀態(tài)的方法全部定義在這里面,組件借助這個(gè)storeMonitor去修改store中的數(shù)據(jù),相當(dāng)于是一個(gè)門面模式。這樣的好處是,組件間接地去修改全局狀態(tài),相當(dāng)于建立了一個(gè)隔離層。另一方面,所有的commit操作都集中在這個(gè)文件中,一目了然。

 

狀態(tài)驅(qū)動(dòng)

何為狀態(tài)驅(qū)動(dòng)

狀態(tài)驅(qū)動(dòng)也可以說(shuō)是數(shù)據(jù)驅(qū)動(dòng),只不過(guò)數(shù)據(jù)是具體存在的(比如一個(gè)js對(duì)象),“狀態(tài)”是抽象出來(lái)的一種描述。狀態(tài)驅(qū)動(dòng)就是指代碼邏輯集中在數(shù)據(jù)操作, 而不是DOM操作以及樣式操作。

舉個(gè)例子,一個(gè)表單提交按鈕,不可點(diǎn)擊的時(shí)候要灰色背景,可點(diǎn)擊的時(shí)候要藍(lán)色背景。那么我們通過(guò)一個(gè)js變量disabled來(lái)控制,大致代碼如下:

<button :class="disabled ? 'bg-gray' : 'bg-blue'">提交</button>

這不就是mvvm雙向綁定的終極奧義嘛,說(shuō)了半天廢話。

其實(shí)上面的代碼是有問(wèn)題的。如果你隱隱覺(jué)得bg-gray、bg-blue這倆名字有點(diǎn)別扭,甚至那個(gè)disabled也看著不順眼,那么你有可能要理解我想說(shuō)什么了。

問(wèn)題在哪里呢?想想這段代碼表達(dá)了什么語(yǔ)義?!鞍粹o不可用的時(shí)候給灰色背景,可用的時(shí)候給藍(lán)色背景”,這,明明還是DOM世界的說(shuō)法嘛。只是包上了雙向綁定的皮而已,根本不是狀態(tài)驅(qū)動(dòng)。

而狀態(tài)驅(qū)動(dòng)的精髓,是要保留業(yè)務(wù)邏輯,消滅和DOM、樣式有關(guān)的一切思維。而我們真正的業(yè)務(wù)邏輯可能是什么呢?“校驗(yàn)通過(guò)的時(shí)候讓按鈕可用,不通過(guò)的時(shí)候失效”。所以,正確的代碼應(yīng)該這么寫:

<button :class="validate ? 'enable' : 'disabled'">提交</button>

什么?別騙我!你只是改了命名而已。

我沒(méi)騙你,“命名即思維“,這是我一貫堅(jiān)持的準(zhǔn)則,胡亂給變量命名的人必然有一顆亂成麻團(tuán)的腦袋。等你明白了舍生取義的道理,自然會(huì)回來(lái)和我一起念:「命名即思維」。

把頁(yè)面上的所有功能都完整的抽象成狀態(tài),那就是狀態(tài)驅(qū)動(dòng)了,而這狀態(tài),不是樣式的狀態(tài)。那么,如何擁有正確的狀態(tài)驅(qū)動(dòng)思維呢?答案就是:面向?qū)ο蟆?/p>

面向?qū)ο蟮乃季S

不看表象,看抽象。前端所要有的面向?qū)ο笏季S差不多就是這樣。

表象是啥呢?是輸入框,是彈出層,是列表,是表格,是花里胡哨的各種顏色。

抽象是啥呢?是用戶名,是密碼,是登陸狀態(tài),是各種業(yè)務(wù)數(shù)據(jù)。我們把頁(yè)面的內(nèi)容抽象成對(duì)象的屬性,把交互抽象成對(duì)象的方法。

還是舉個(gè)例子吧,看下面這個(gè)丑陋的原型圖:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

那我們抽象出來(lái)的對(duì)象應(yīng)該大致這樣:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

{
    businessOptions: [],
    currentIndex: 0,
    selectedList: [],
    select: function(index){ //選中操作  }
    remove: function(index){ //刪除操作   }}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

我們的代碼邏輯應(yīng)該是切換currentIndex,以及調(diào)用select方法來(lái)添加選項(xiàng)到selectedList數(shù)組。如果你想用active來(lái)表示當(dāng)前激活的tab,或者是用left/right表示左邊/右邊兩欄,那就大大的犯了表象主義錯(cuò)誤。

在寫小游戲的時(shí)候可能用到的面向?qū)ο笏季S較多,組件化開(kāi)發(fā)中,也應(yīng)當(dāng)用這個(gè)思維去做整體設(shè)計(jì)。一個(gè)組件就是很具象的實(shí)體,所以要將之“物件化”。

css也要“狀態(tài)”

css作為樣式的描述語(yǔ)言,其命名方式以及組織方式有多種規(guī)則。在狀態(tài)驅(qū)動(dòng)的開(kāi)發(fā)思維下,我傾向讓css也具有“描述狀態(tài)”的能力。看下面的一段sass代碼:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

.sidebar{
    position: absolute;
    bottom: 0;
    width: 80%;
    &.show{
        display: block;
    }
    &.hidden{
        display: none;
    }
    .btn{
        display: inline-block;
        width: 200px;
        height: 20px;
    }
    &.open{
        left: 0;
        .btn{
            background-image: url(left.png);
        }
    }
    &.close{
        left: -80%;
        .btn{
            background-image: url(right.png);
        }
    }
}

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

光看css,不看js代碼的情況下,我們已經(jīng)可以得知界面的展示邏輯了:有一個(gè)名為sidebar的側(cè)邊欄,它有四種狀態(tài),分別是:show、hidden、open、close。sidebar下有一個(gè)按鈕btn,它在sidebar打開(kāi)的時(shí)候是向左的背景圖,在sidebar關(guān)閉的時(shí)候是向右的背景圖。

這樣一套結(jié)構(gòu)清晰,語(yǔ)義明確的css規(guī)則,能夠幫助我們很快理清頁(yè)面邏輯,別人在看你的代碼的時(shí)候一目了然。上面只是一個(gè)簡(jiǎn)單的例子,實(shí)踐的時(shí)候會(huì)有復(fù)雜的場(chǎng)景,可根據(jù)具體功能劃分出各自的作用域(嵌套語(yǔ)法),稍微需要花時(shí)間去設(shè)計(jì),換來(lái)的是清晰的代碼。

不需要?jiǎng)討B(tài)創(chuàng)建組件

用mvvm框架去寫彈框組件的時(shí)候,往往會(huì)有這樣一個(gè)困惑:在jquery時(shí)代,我們通過(guò) $.msg('內(nèi)容')這樣的方式調(diào)用彈框,此時(shí)在頁(yè)面上動(dòng)態(tài)創(chuàng)建一個(gè)節(jié)點(diǎn),關(guān)閉彈框的時(shí)候再把節(jié)點(diǎn)移除。習(xí)慣于此,我們很希望能用同樣的方式來(lái)處理彈框。

當(dāng)然這在vue中也是可以做到的,方式就是動(dòng)態(tài)創(chuàng)建標(biāo)簽,并且動(dòng)態(tài)new一個(gè)組件實(shí)例去渲染它,在監(jiān)聽(tīng)到close消息時(shí),把這個(gè)節(jié)點(diǎn)手動(dòng)刪掉。大體代碼如下:

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

const MessageConstructor = Vue.extend(alert);
 
const Message = (config) => {
   instance = new MessageConstructor({
       el: document.createElement('div')
   });
   document.body.appendChild(instance.$el);
 
   Vue.nextTick(()=>{
       instance.show = true;
       instance.content = config.content || '';
       instance.type = config.type || 'danger';
       instance.$on('close', function(){           this.show = false;
           document.body.removeChild(this.$el);
       });
       instance.$on('confirm', config.onConfirm)
   });
}

export default Message;

電腦培訓(xùn),計(jì)算機(jī)培訓(xùn),平面設(shè)計(jì)培訓(xùn),網(wǎng)頁(yè)設(shè)計(jì)培訓(xùn),美工培訓(xùn),Web培訓(xùn),Web前端開(kāi)發(fā)培訓(xùn)

這樣的方式確實(shí)可以實(shí)現(xiàn),但是其思想?yún)s是和狀態(tài)驅(qū)動(dòng)違背的,某個(gè)應(yīng)用在某時(shí)某刻彈窗,這可以理解為這個(gè)應(yīng)用的狀態(tài),我們只需用一個(gè)變量來(lái)標(biāo)記該狀態(tài)即可,犯不著手動(dòng)創(chuàng)建節(jié)點(diǎn)、刪除節(jié)點(diǎn)這么大動(dòng)干戈。事實(shí)上vue作者也推崇這樣來(lái)處理彈窗,節(jié)點(diǎn)始終掛載在頁(yè)面,需要彈的時(shí)候給顯示即可。

本篇結(jié)束,以上是筆者在實(shí)際開(kāi)發(fā)者總結(jié)出的最佳實(shí)踐,當(dāng)然這只是一個(gè)開(kāi)發(fā)模式,并無(wú)對(duì)錯(cuò)。大家可以參考,或引發(fā)其他思考。

http://www.cnblogs.com/lvdabao/p/vue-component.html