「不容錯過」34條 Vue 高質(zhì)量實戰(zhàn)技巧
這是我學(xué)習(xí)整理的關(guān)于 Vue.js 系列文章的第一篇,另外還有兩篇分別是關(guān)于優(yōu)化和原理的。希望讀完這3篇文章,你能對 Vue 有個更深入的認(rèn)識。
7種組件通信方式隨你選
組件通信是 Vue 的核心知識,掌握這幾個知識點,面試開發(fā)一點問題都沒有。
props/@on+$emit
用于實現(xiàn)父子組件間通信。通過 props 可以把父組件的消息傳遞給子組件:
//?parent.vue????
<child?:title="title">child>
//?child.vue
props:?{
????title:?{
????????type:?String,
????????default:?'',
????}
}
這樣一來 this.title 就直接拿到從父組件中傳過來的 title 的值了。注意,你不應(yīng)該在子組件內(nèi)部直接改變 prop,這里就不多贅述,可以直接看官網(wǎng)介紹。
而通過 @on+$emit 組合可以實現(xiàn)子組件給父組件傳遞信息:
//?parent.vue
<child?@changeTitle="changeTitle">child>
//?child.vue
this.$emit('changeTitle',?'bubuzou.com')
和listeners
Vue_2.4 中新增的 $attrs/$listeners 可以進行跨級的組件通信。$attrs 包含了父級作用域中不作為 prop 的屬性綁定(class 和 style 除外),好像聽起來有些不好理解?沒事,看下代碼就知道是什么意思了:
//?父組件?index.vue
<list?class="list-box"?title="標(biāo)題"?desc="描述"?:list="list">list>
//?子組件?list.vue
props:?{
????list:?[],
},
mounted()?{
????console.log(this.$attrs)??//?{title:?"標(biāo)題",?desc:?"描述"}
}
在上面的父組件 index.vue 中我們給子組件 list.vue 傳遞了4個參數(shù),但是在子組件內(nèi)部 props 里只定義了一個 list,那么此時 this.$attrs 的值是什么呢?首先要去除 props 中已經(jīng)綁定了的,然后再去除 class 和 style,最后剩下 title 和 desc 結(jié)果和打印的是一致的?;谏厦娲a的基礎(chǔ)上,我們在給 list.vue 中加一個子組件:
// 子組件 list.vue
//?孫子組件?detail.vue
//?不定義props,直接打印?$attrs
mounted()?{
????console.log(this.$attrs)??//?{title:?"標(biāo)題",?desc:?"描述"}
}
在子組件中我們定義了一個 v-bind="$attrs" 可以把父級傳過來的參數(shù),去除 props、class 和 style 之后剩下的繼續(xù)往下級傳遞,這樣就實現(xiàn)了跨級的組件通信。
$attrs 是可以進行跨級的參數(shù)傳遞,實現(xiàn)父到子的通信;同樣的,通過 $listeners 用類似的操作方式可以進行跨級的事件傳遞,實現(xiàn)子到父的通信。$listeners 包含了父作用域中不含 .native 修飾的 v-on 事件監(jiān)聽器,通過 v-on="$listeners" 傳遞到子組件內(nèi)部。
//?父組件?index.vue
<list?@change="change"?@update.native="update">list>
//?子組件?list.vue
<detail?v-on="$listeners">detail>
//?孫子組件?detail.vue
mounted()?{
????this.$listeners.change()
????this.$listeners.update()?//?TypeError:?this.$listeners.update?is?not?a?function
}
provide/inject組合拳
provide/inject 組合以允許一個祖先組件向其所有子孫后代注入一個依賴,可以注入屬性和方法,從而實現(xiàn)跨級父子組件通信。在開發(fā)高階組件和組件庫的時候尤其好用。
//?父組件?index.vue
data()?{
????return?{
????????title:?'bubuzou.com',
????}
}
provide()?{
????return?{
????????detail:?{
????????????title:?this.title,
????????????change:?(val)?=>?{
????????????????console.log(?val?)
????????????}
????????}
????}
}
//?孫子組件?detail.vue
inject:?['detail'],
mounted()?{
????console.log(this.detail.title)??//?bubuzou.com
????this.detail.title?=?'hello?world'??//?雖然值被改變了,但是父組件中?title?并不會重新渲染
????this.detail.change('改變后的值')??//?執(zhí)行這句后將打?。焊淖兒蟮闹?
}
??
provide和inject的綁定對于原始類型來說并不是可響應(yīng)的。這是刻意為之的。然而,如果你傳入了一個可監(jiān)聽的對象,那么其對象的 property 還是可響應(yīng)的。這也就是為什么在孫子組件中改變了title,但是父組件不會重新渲染的原因。
EventBus
以上三種方式都是只能從父到子方向或者子到父方向進行組件的通信,而我就比較牛逼了?,我還能進行兄弟組件之間的通信,甚至任意2個組件間通信。利用 Vue 實例實現(xiàn)一個 EventBus 進行信息的發(fā)布和訂閱,可以實現(xiàn)在任意2個組件之間通信。有兩種寫法都可以初始化一個 eventBus 對象:
通過導(dǎo)出一個
Vue實例,然后再需要的地方引入://?eventBus.js
import?Vue?from?'vue'
export?const?EventBus?=?new?Vue()使用
EventBus訂閱和發(fā)布消息:import?{EventBus}?from?'../utils/eventBus.js'
//?訂閱處
EventBus.$on('update',?val?=>?{})
//?發(fā)布處
EventBus.$emit('update',?'更新信息')在
main.js中初始化一個全局的事件總線://?main.js
Vue.prototype.$eventBus?=?new?Vue()使用:
//?需要訂閱的地方
this.$eventBus.$on('update',?val?=>?{})
//?需要發(fā)布信息的地方
this.$eventBus.$emit('update',?'更新信息')
如果想要移除事件監(jiān)聽,可以這樣來:
this.$eventBus.$off('update',?{})
上面介紹了兩種寫法,推薦使用第二種全局定義的方式,可以避免在多處導(dǎo)入 EventBus 對象。這種組件通信方式只要訂閱和發(fā)布的順序得當(dāng),且事件名稱保持唯一性,理論上可以在任何 2 個組件之間進行通信,相當(dāng)?shù)膹姶?。但是方法雖好,可不要濫用,建議只用于簡單、少量業(yè)務(wù)的項目中,如果在一個大型繁雜的項目中無休止的使用該方法,將會導(dǎo)致項目難以維護。
Vuex進行全局的數(shù)據(jù)管理
Vuex 是一個專門服務(wù)于 Vue.js 應(yīng)用的狀態(tài)管理工具。適用于中大型應(yīng)用。Vuex 中有一些專有概念需要先了解下:
State:用于數(shù)據(jù)的存儲,是store中的唯一數(shù)據(jù)源;Getter:類似于計算屬性,就是對State中的數(shù)據(jù)進行二次的處理,比如篩選和對多個數(shù)據(jù)進行求值等;Mutation:類似事件,是改變Store中數(shù)據(jù)的唯一途徑,只能進行同步操作;Action:類似Mutation,通過提交Mutation來改變數(shù)據(jù),而不直接操作State,可以進行異步操作;Module:當(dāng)業(yè)務(wù)復(fù)雜的時候,可以把store分成多個模塊,便于維護;
對于這幾個概念有各種對應(yīng)的 map 輔助函數(shù)用來簡化操作,比如 mapState,如下三種寫法其實是一個意思,都是為了從 state 中獲取數(shù)據(jù),并且通過計算屬性返回給組件使用。
computed:?{
????count()?{
????????return?this.$store.state.count
????},
????...mapState({
????????count:?state?=>?state.count
????}),
????...mapState(['count']),
},
又比如 mapMutations, 以下兩種函數(shù)的定義方式要實現(xiàn)的功能是一樣的,都是要提交一個 mutation 去改變 state 中的數(shù)據(jù):
methods:?{
????increment()?{
????????this.$store.commit('increment')
????},
????...mapMutations(['increment']),
}
接下來就用一個極簡的例子來展示 Vuex 中任意2個組件間的狀態(tài)管理。1、 新建 store.js
import?Vue?from?'vue'
import?Vuex?from?'vuex'
Vue.use(Vuex)
????
export?default?new?Vuex.Store({
????state:?{
????????count:?0,
????},
????mutations:?{
????????increment(state)?{
????????????state.count++
????????},
????????decrement(state)?{
????????????state.count--
????????}
????},
})
2、 創(chuàng)建一個帶 store 的 Vue 實例
import?Vue?from?'vue'
import?App?from?'./App.vue'
import?router?from?'./router'
import?store?from?'./utils/store'
????
new?Vue({
????router,
????store,
????render:?h?=>?h(App)
}).$mount('#app')
3、 任意組件 A 實現(xiàn)點擊遞增
<template>
????<p?@click="increment">click to increment:{{count}}p>
template>
<script>
import?{mapState,?mapMutations}?from?'vuex'
export?default?{
????computed:?{
????????...mapState(['count'])
????},
????methods:?{
????????...mapMutations(['increment'])
????},
}
script>
4、 任意組件 B 實現(xiàn)點擊遞減
<template>
????<p?@click="decrement">click to decrement:{{count}}p>
template>
<script>
import?{mapState,?mapMutations}?from?'vuex'
export?default?{
????computed:?{
????????...mapState(['count'])
????},
????methods:?{
????????...mapMutations(['decrement'])
????},
}
script>
以上只是用最簡單的 vuex 配置去實現(xiàn)組件通信,當(dāng)然真實項目中的配置肯定會更復(fù)雜,比如需要對 State 數(shù)據(jù)進行二次篩選會用到 Getter,然后如果需要異步的提交那么需要使用 Action,再比如如果模塊很多,可以將 store 分模塊進行狀態(tài)管理。對于 Vuex 更多復(fù)雜的操作還是建議去看Vuex 官方文檔,然后多寫例子。
Vue.observable實現(xiàn)mini vuex
這是一個 Vue2.6 中新增的 API,用來讓一個對象可以響應(yīng)。我們可以利用這個特點來實現(xiàn)一個小型的狀態(tài)管理器。
//?store.js
import?Vue?from?'vue'
?
export?const?state?=?Vue.observable({
????count:?0,
})
export?const?mutations?=?{
????increment()?{
????????state.count++
????}
????decrement()?{
????????state.count--
????}
}
//?parent.vue
<template>
????<p>{{?count?}}p>
template>
<script>
import?{?state?}?from?'../store'
export?default?{
????computed:?{
????????count()?{
????????????return?state.count
????????}
????}
}
script>
//?child.vue
import??{?mutations?}?from?'../store'
export?default?{
????methods:?{
????????handleClick()?{
????????????mutations.increment()
????????}
????}
}
children/root
通過給子組件定義 ref 屬性可以使用 $refs 來直接操作子組件的方法和屬性。
<child?ref="list">child>
比如子組件有一個 getList 方法,可以通過如下方式進行調(diào)用,實現(xiàn)父到子的通信:
this.$refs.list.getList()
除了 $refs 外,其他3個都是自 Vue 實例創(chuàng)建后就會自動包含的屬性,使用和上面的類似。
6類可以掌握的修飾符
表單修飾符
表單類的修飾符都是和 v-model 搭配使用的,比如:v-model.lazy、v-model-trim 以及 v-model.number 等。
.lazy:對表單輸入的結(jié)果進行延遲響應(yīng),通常和v-model搭配使用。正常情況下在input里輸入內(nèi)容會在p標(biāo)簽里實時的展示出來,但是加上.lazy后則需要在輸入框失去焦點的時候才觸發(fā)響應(yīng)。<input?type="text"?v-model.lazy="name"?/>
<p>{{?name?}}p>.trim:過濾輸入內(nèi)容的首尾空格,這個和直接拿到字符串然后通過str.trim()去除字符串首尾空格是一個意思。.number:如果輸入的第一個字符是數(shù)字,那就只能輸入數(shù)字,否則他輸入的就是普通字符串。
事件修飾符
Vue 的事件修飾符是專門為 v-on 設(shè)計的,可以這樣使用:@click.stop="handleClick",還能串聯(lián)使用:@click.stop.prevent="handleClick"。
<div?@click="doDiv">
????click?div
????<p?@click="doP">click?pp>
div>
.stop:阻止事件冒泡,和原生event.stopPropagation()是一樣的效果。如上代碼,當(dāng)點擊p標(biāo)簽的時候,div上的點擊事件也會觸發(fā),加上.stop后事件就不會往父級傳遞,那父級的事件就不會觸發(fā)了。.prevent:阻止默認(rèn)事件,和原生的event.preventDefault()是一樣的效果。比如一個帶有href的鏈接上添加了點擊事件,那么事件觸發(fā)的時候也會觸發(fā)鏈接的跳轉(zhuǎn),但是加上.prevent后就不會觸發(fā)鏈接跳轉(zhuǎn)了。.capture:默認(rèn)的事件流是:捕獲階段-目標(biāo)階段-冒泡階段,即事件從最具體目標(biāo)元素開始觸發(fā),然后往上冒泡。而加上.capture后則是反過來,外層元素先觸發(fā)事件,然后往深層傳遞。.self:只觸發(fā)自身的事件,不會傳遞到父級,和.stop的作用有點類似。.once:只會觸發(fā)一次該事件。.passive:當(dāng)頁面滾動的時候就會一直觸發(fā)onScroll事件,這個其實是存在性能問題的,尤其是在移動端,當(dāng)給他加上.passive后觸發(fā)的就不會那么頻繁了。.native:現(xiàn)在在組件上使用v-on只會監(jiān)聽自定義事件 (組件用$emit觸發(fā)的事件)。如果要監(jiān)聽根元素的原生事件,可以使用.native修飾符,比如如下的el-input,如果不加.native當(dāng)回車的時候就不會觸發(fā)search函數(shù)。<el-input?type="text"?v-model="name"?@keyup.enter.native="search">el-input>
?串聯(lián)使用事件修飾符的時候,需要注意其順序,同樣2個修飾符進行串聯(lián)使用,順序不同,結(jié)果大不一樣。
?@click.prevent.self會阻止所有的點擊事件,而@click.self.prevent只會阻止對自身元素的點擊。
鼠標(biāo)按鈕修飾符
.left:鼠標(biāo)左鍵點擊;.right:鼠標(biāo)右鍵點擊;.middle:鼠標(biāo)中鍵點擊;
鍵盤按鍵修飾符
Vue 提供了一些常用的按鍵碼:
.enter.tab.delete(捕獲“刪除”和“退格”鍵).esc.space.up.down.left.right
另外,你也可以直接將 KeyboardEvent.key 暴露的任意有效按鍵名轉(zhuǎn)換為 kebab-case 來作為修飾符,比如可以通過如下的代碼來查看具體按鍵的鍵名是什么:
<input?@keyup="onKeyUp">
onKeyUp(event)?{
????console.log(event.key)??//?比如鍵盤的方向鍵向下就是?ArrowDown
}
.exact修飾符
.exact 修飾符允許你控制由精確的系統(tǒng)修飾符組合觸發(fā)的事件。
<button?v-on:click.ctrl="onClick">Abutton>
<button?v-on:click.ctrl.exact="onCtrlClick">Abutton>
<button?v-on:click.exact="onClick">Abutton>
.sync修飾符
.sync 修飾符常被用于子組件更新父組件數(shù)據(jù)。直接看下面的代碼:
//?parent.vue
<child?:title.sync="title">child>
//?child.vue
this.$emit('update:title',?'hello')
子組件可以直接通過 update:title 的形式進行更新父組件中聲明了 .sync 的 prop。上面父組件中的寫法其實是下面這種寫法的簡寫:
<child?:title="title"?@update:title="title?=?$event">child>
?注意帶有 .sync 修飾符的 v-bind 不能和表達式一起使用
?
如果需要設(shè)置多個 prop,比如:
<child?:name.sync="name"?:age.sync="age"?:sex.sync="sex">child>
可以通過 v-bind.sync 簡寫成這樣:
<child?v-bind.sync="person">child>
person:?{
????name:?'bubuzou',
????age:?21,
????sex:?'male',
}
Vue 內(nèi)部會自行進行解析把 person 對象里的每個屬性都作為獨立的 prop 傳遞進去,各自添加用于更新的 v-on 監(jiān)聽器。而從子組件進行更新的時候還是保持不變,比如:
this.$emit('update:name',?'hello')
6種方式編寫可復(fù)用模塊
今天需求評審了一個需求,需要實現(xiàn)一個詳情頁,這個詳情頁普通用戶和管理員都能進去,但是展示的數(shù)據(jù)有稍有不同,但絕大部分是一樣的;最主要的區(qū)別是詳情對于普通用戶是純展示,而對于管理員要求能夠編輯,然后管理員還有一些別的按鈕權(quán)限等。需求看到這里,如果在排期的時候把用戶的詳情分給開發(fā)A做,而把管理員的詳情分給B去做,那這樣做的結(jié)果就是開發(fā)A寫了一個詳情頁,開發(fā)B寫了一個詳情頁,這在開發(fā)階段、提測后的修改 bug 階段以及后期迭代階段,都需要同時維護這 2 個文件,浪費了時間浪費了人力,所以你可以從中意識到編寫可復(fù)用模塊的重要性。
而 Vue 作者尤大為了讓開發(fā)者更好的編寫可復(fù)用模塊,提供了很多的手段,比如:組件、自定義指令、渲染函數(shù)、插件以及過濾器等。
組件
組件是 Vue 中最精髓的地方,也是我們平時編寫可復(fù)用模塊最常用的手段,但是由于這塊內(nèi)容篇幅很多,所以不在這里展開,后續(xù)會寫相關(guān)的內(nèi)容進行詳述。
使用混入mixins
什么是混入呢?從代碼結(jié)構(gòu)上來看,混入其實就是半個組件,一個 Vue 組件可以包括 template、script 和 style 三部分,而混入其實就是 script 里面的內(nèi)容。一個混入對象包含任意組件選項,比如 data、methods、computed、watch 、生命周期鉤子函數(shù)、甚至是 mixins 自己等,混入被設(shè)計出來就是旨在提高代碼的靈活性、可復(fù)用性。
什么時候應(yīng)該使用混入呢?當(dāng)可復(fù)用邏輯只是 JS 代碼層面的,而無 template 的時候就可以考慮用混入了。比如需要記錄用戶在頁面的停留的時間,那我們就可以把這段邏輯抽出來放在 mixins 里:
//?mixins.js
export?const?statMixin?=?{
????methods:?{
????????enterPage()?{},
????????leavePage()?{},
????},
????mounted()?{
????????this.enterPage()
????},
????beforeDestroyed()?{
????????this.leavePage()
????}
}
然后在需要統(tǒng)計頁面停留時間的地方加上:
import?{?statMixin?}?from?'../common/mixins'
export?default?{
????mixins:?[statMixin]
}
使用混入的時候要注意和組件選項的合并規(guī)則,可以分為如下三類:
data將進行遞歸合并,對于鍵名沖突的以組件數(shù)據(jù)為準(zhǔn)://?mixinA?的?data
data()?{
????obj:?{
????????name:?'bubuzou',
????},
}
//?component?A
export?default?{
????mixins:?[mixinA],
????data(){
????????obj:?{
????????????name:?'hello',
????????????age:?21
????????},
????},
????mounted()?{
????????console.log(?this.obj?)??//?{?name:?'bubuzou',?'age':?21?}????
????}
}對于生命周期鉤子函數(shù)將會合并成一個數(shù)組,混入對象的鉤子將先被執(zhí)行:
//?mixin?A
const?mixinA?=?{
????created()?{
????????console.log(?'第一個執(zhí)行'?)
????}
}
//?mixin?B
const?mixinB?=?{
????mixins:?[mixinA]
????created()?{
????????console.log(?'第二個執(zhí)行'?)
????}
}
//?component?A
export?default?{
????mixins:?[mixinB]
????created()?{
????????console.log(?'最后一個執(zhí)行'?)
????}
}值為對象的選項,例如
methods、components和directives,將被合并為同一個對象。兩個對象鍵名沖突時,取組件對象的鍵值對。
自定義指令
除了 Vue 內(nèi)置的一些指令比如 v-model、v-if 等,Vue 還允許我們自定義指令。在 Vue2.0 中,代碼復(fù)用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進行底層操作,這時候就會用到自定義指令。比如我們可以通過自定義一個指令來控制按鈕的權(quán)限。我們期望設(shè)計一個如下形式的指令來控制按鈕權(quán)限:
<button?v-auth="['user']">提交button>
通過在按鈕的指令里傳入一組權(quán)限,如果該按鈕只有 admin 權(quán)限才可以提交,而我們傳入一個別的權(quán)限,比如 user,那這個按鈕就不應(yīng)該顯示了。接下來我們?nèi)プ砸粋€全局的指令:
//?auth.js
const?AUTH_LIST?=?['admin']
function?checkAuth(auths)?{
????return?AUTH_LIST.some(item?=>?auths.includes(item))
}
function?install(Vue,?options?=?{})?{
????Vue.directive('auth',?{
????????inserted(el,?binding)?{
????????????if?(!checkAuth(binding.value))?{
????????????????el.parentNode?&&?el.parentNode.removeChild(el)
????????????}
????????}
????})
}
export?default?{?install?}
然后我們需要在 main.js 里通過安裝插件的方式來啟用這個指令:
import?Auth?from?'./utils/auth'
Vue.use(Auth)
使用渲染函數(shù)
這里將使用渲染函數(shù)實現(xiàn)上面介紹過的的權(quán)限按鈕。使用方式如下,把需要控制權(quán)限的按鈕包在權(quán)限組件 authority 里面,如果有該權(quán)限就顯示,沒有就不顯示。
<authority?:auth="['admin']">
????<button>提交button>
authority>
然后我們用渲染函數(shù)去實現(xiàn)一個 authority 組件:
欧美亚州性爱
|
成人免费TV
|
天天日夜夜
|
欧美大香蕉四级片在线网站成熟
|
色五月婷婷丁香电影网
|
