不管在任何的語言或框架中,咱們都提倡代碼的複用性。對於Vue來講也是如此,相同的代碼邏輯會被封裝成組件,除了複用以外,更重要的是統一管理提升開發效率。我真就接手過一個項目,多個頁面都會用到的列表,沒有去封裝列表組件,只要有一點改動,每一個頁面都得加上。很確定的說,沒有用組件化開發的Vue項目是沒有靈魂的。因此如何封裝一個優雅且複用性高的組件成爲咱們必需的技能。html
首先來看一個Tab組件的實現,看看它存在什麼問題,哪裏能夠改進?vue
<template> <div class="tabs"> <div class="tab-item" :class="{'tab--active':item===activeName}" v-for="(item,index) in tabs" :key="index" @click="tabChange(item)"> {{item}} </div> </div> </template> <script> export default { props:{ tabs:{ type: Array, default: ()=> [] }, activeName:{ type: String, default: '' } }, methods:{ tabChange(item){ this.$emit('tabChange',item) } }, } </script>
<template> <div> <Tabs :tabs="tabs" :activeName="activeName" @tabChange="tabChange" /> </div> </template> <script> import Tabs from '../components/Tabs' export default { components:{ Tabs }, data(){ return{ tabs:['黃金體驗','敗者食塵','緋紅之王','白金之星','波紋疾走'], activeName: '黃金體驗' } }, methods:{ tabChange(item){ this.activeName = item } }, } </script>
這個組件最大的問題就是,activeName 須要使用者額外經過事件來手動更新,假若有另外一個使用者接手,在不知道這種狀況下使用,會出現tab沒有切換的狀況。而後要去看組件內部實現,再回來修改代碼,很顯然這樣的組件是失敗的。本着全部的髒活累活都由組件實現的原則,理想的狀態應該是使用者不須要管理 activeName,而是由組件內部去更新。api
可能有人會想到,既然要由內部管理,那在組件內部修改prop的值是否是就能夠了?來看下這樣的作法是否可行
修改組件tabChange方法,在點擊時更新prop的值框架
tabChange(item){ this.activeName = item this.$emit('tabChange',item) }
使用時,控制檯拋出警告
因爲prop是單向數據流,父級prop的更新會向下流動到子組件中,相反的在子組件內部直接更新狀態,會致使數據的流向不明確。例如,在父組件中有多個子組件依賴同一個屬性,其中一個子組件更新該屬性,會引起其他子組件發生改變,發生問題時不容易被找到,所以Vue不推薦咱們這樣作。另外,在父組件發生更新時,子組件的prop會被刷新爲最新的值。
單向數據流: https://cn.vuejs.org/v2/guide/components-props.html#%E5%8D%95%E5%90%91%E6%95%B0%E6%8D%AE%E6%B5%81ide
組件model選項組件化
容許一個自定義組件在使用 v-model 時定製 prop 和 event。默認狀況下,一個組件上的 v-model 會把 value 用做 prop 且把 input 用做 event,可是一些輸入類型好比單選框和複選框按鈕可能想使用 value prop 來達到不一樣的目的。使用 model 選項能夠迴避這些狀況產生的衝突。ui
model: https://cn.vuejs.org/v2/api/#modelthis
在model選項裏,咱們能夠綁定一個屬性,併爲其添加事件,只需在調用方法時傳入值便可更新屬性。雙向綁定
<script> export default { model:{ prop: 'activeName', event: 'update' }, props:{ tabs:{ type: Array, default: ()=> [] }, activeName:{ type: String, default: '' } }, methods:{ tabChange(item){ this.$emit('update',item) // 這裏更新activeName this.$emit('tabChange',item) } } } </script>
注意你仍然須要在組件的 props 選項裏聲明 prop。code
使用組件雙向綁定後,屬性在組件內部被更新時,父組件的 activeName 也會隨之更新,這樣使用者能夠很明確的知道數據可能會被修改。
<Tabs :tabs="tabs" v-model="activeName" />
使用組件的model選項實現自定義組件雙向綁定,在組件內部經過事件更新屬性值,這樣的自定義組件使用起來更優雅。其實經過model選項的方式去修改父級屬性,我認爲有點違反了單向數據流的原則。原本單向數據流是不容許子級修改父級屬性的,只是使用v-model的語法糖,看起來會讓數據流向顯得更加明確,剛好彌補這個缺點。