vue 點選事件傳遞多個引數_不容錯過34條 Vue 高質量實戰技巧
技術標籤:vue 點選事件傳遞多個引數
這是我學習整理的關於 Vue.js
系列文章的第一篇,另外還有兩篇分別是關於優化和原理的。希望讀完這3篇文章,你能對 Vue
有個更深入的認識。
7種元件通訊方式隨你選
元件通訊是 Vue
的核心知識,掌握這幾個知識點,面試開發一點問題都沒有。
props/@on+$emit
用於實現父子元件間通訊。通過 props
可以把父元件的訊息傳遞給子元件:
//parent.vue
<child:title="title">child>
//child.vue
props:{
title:{
type:String,
default:'',
}
}
這樣一來 this.title
就直接拿到從父元件中傳過來的 title
的值了。注意,你不應該在子元件內部直接改變 prop
,這裡就不多贅述,可以直接看官網介紹。
而通過 @on+$emit
組合可以實現子元件給父元件傳遞資訊:
//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
<listclass="list-box"title="標題"desc="描述":list="list">list>
//子元件list.vue
props:{
list:[],
},
mounted(){
console.log(this.$attrs)//{title:"標題",desc:"描述"}
}
在上面的父元件 index.vue
中我們給子元件 list.vue
props
裡只定義了一個 list
,那麼此時 this.$attrs
的值是什麼呢?首先要去除 props
中已經綁定了的,然後再去除 class
和 style
,最後剩下 title
和 desc
結果和列印的是一致的。基於上面程式碼的基礎上,我們在給 list.vue
中加一個子元件:
// 子元件 list.vue
//孫子元件detail.vue
//不定義props,直接列印$attrs
mounted(){
console.log(this.$attrs)//{title:"標題",desc:"描述"}
}
在子元件中我們定義了一個 v-bind="$attrs"
可以把父級傳過來的引數,去除 props
、class
和 style
之後剩下的繼續往下級傳遞,這樣就實現了跨級的元件通訊。
$attrs
是可以進行跨級的引數傳遞,實現父到子的通訊;同樣的,通過 $listeners
用類似的操作方式可以進行跨級的事件傳遞,實現子到父的通訊。$listeners
包含了父作用域中不含 .native
修飾的 v-on
事件監聽器,通過 v-on="$listeners"
傳遞到子元件內部。
//父元件index.vue
<list@change="change"@update.native="update">list>
//子元件list.vue
<detailv-on="$listeners">detail>
//孫子元件detail.vue
mounted(){
this.$listeners.change()
this.$listeners.update()//TypeError:this.$listeners.updateisnotafunction
}
provide/inject組合拳
provide/inject
組合以允許一個祖先元件向其所有子孫後代注入一個依賴,可以注入屬性和方法,從而實現跨級父子元件通訊。在開發高階元件和元件庫的時候尤其好用。
//父元件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='helloworld'//雖然值被改變了,但是父元件中title並不會重新渲染
this.detail.change('改變後的值')//執行這句後將列印:改變後的值
}
❝❞
provide
和inject
的繫結對於原始型別來說並不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的物件,那麼其物件的 property 還是可響應的。這也就是為什麼在孫子元件中改變了title
,但是父元件不會重新渲染的原因。
EventBus
以上三種方式都是隻能從父到子方向或者子到父方向進行元件的通訊,而我就比較牛逼了?,我還能進行兄弟元件之間的通訊,甚至任意2個元件間通訊。利用 Vue
例項實現一個 EventBus
進行資訊的釋出和訂閱,可以實現在任意2個元件之間通訊。有兩種寫法都可以初始化一個 eventBus
物件:
通過匯出一個
Vue
例項,然後再需要的地方引入://eventBus.js
importVuefrom'vue'
exportconstEventBus=newVue()使用
EventBus
訂閱和釋出訊息:import{EventBus}from'../utils/eventBus.js'
//訂閱處
EventBus.$on('update',val=>{})
//釋出處
EventBus.$emit('update','更新資訊')在
main.js
中初始化一個全域性的事件匯流排://main.js
Vue.prototype.$eventBus=newVue()使用:
//需要訂閱的地方
this.$eventBus.$on('update',val=>{})
//需要釋出資訊的地方
this.$eventBus.$emit('update','更新資訊')
如果想要移除事件監聽,可以這樣來:
this.$eventBus.$off('update',{})
上面介紹了兩種寫法,推薦使用第二種全域性定義的方式,可以避免在多處匯入 EventBus
物件。這種元件通訊方式只要訂閱和釋出的順序得當,且事件名稱保持唯一性,理論上可以在任何 2 個元件之間進行通訊,相當的強大。但是方法雖好,可不要濫用,建議只用於簡單、少量業務的專案中,如果在一個大型繁雜的專案中無休止的使用該方法,將會導致專案難以維護。
Vuex進行全域性的資料管理
Vuex
是一個專門服務於 Vue.js
應用的狀態管理工具。適用於中大型應用。Vuex
中有一些專有概念需要先了解下:
State
:用於資料的儲存,是store
中的唯一資料來源;Getter
:類似於計算屬性,就是對State
中的資料進行二次的處理,比如篩選和對多個數據進行求值等;Mutation
:類似事件,是改變Store
中資料的唯一途徑,只能進行同步操作;Action
:類似Mutation
,通過提交Mutation
來改變資料,而不直接操作State
,可以進行非同步操作;Module
:當業務複雜的時候,可以把store
分成多個模組,便於維護;
對於這幾個概念有各種對應的 map
輔助函式用來簡化操作,比如 mapState
,如下三種寫法其實是一個意思,都是為了從 state
中獲取資料,並且通過計算屬性返回給元件使用。
computed:{
count(){
returnthis.$store.state.count
},
...mapState({
count:state=>state.count
}),
...mapState(['count']),
},
又比如 mapMutations
, 以下兩種函式的定義方式要實現的功能是一樣的,都是要提交一個 mutation
去改變 state
中的資料:
methods:{
increment(){
this.$store.commit('increment')
},
...mapMutations(['increment']),
}
接下來就用一個極簡的例子來展示 Vuex
中任意2個元件間的狀態管理。1、 新建 store.js
importVuefrom'vue'
importVuexfrom'vuex'
Vue.use(Vuex)
exportdefaultnewVuex.Store({
state:{
count:0,
},
mutations:{
increment(state){
state.count++
},
decrement(state){
state.count--
}
},
})
2、 建立一個帶 store
的 Vue
例項
importVuefrom'vue'
importAppfrom'./App.vue'
importrouterfrom'./router'
importstorefrom'./utils/store'
newVue({
router,
store,
render:h=>h(App)
}).$mount('#app')
3、 任意元件 A
實現點選遞增
<template>
<p@click="increment">click to increment:{{count}}p>
template>
<script>import{mapState,mapMutations}from'vuex'exportdefault{computed:{
...mapState(['count'])
},methods:{
...mapMutations(['increment'])
},
}script>
4、 任意元件 B
實現點選遞減
<template>
<p@click="decrement">click to decrement:{{count}}p>
template>
<script>import{mapState,mapMutations}from'vuex'exportdefault{computed:{
...mapState(['count'])
},methods:{
...mapMutations(['decrement'])
},
}script>
以上只是用最簡單的 vuex
配置去實現元件通訊,當然真實專案中的配置肯定會更復雜,比如需要對 State
資料進行二次篩選會用到 Getter
,然後如果需要非同步的提交那麼需要使用 Action
,再比如如果模組很多,可以將 store
分模組進行狀態管理。對於 Vuex
更多複雜的操作還是建議去看Vuex 官方文件,然後多寫例子。
Vue.observable實現mini vuex
這是一個 Vue2.6
中新增的 API
,用來讓一個物件可以響應。我們可以利用這個特點來實現一個小型的狀態管理器。
//store.js
importVuefrom'vue'
exportconststate=Vue.observable({
count:0,
})
exportconstmutations={
increment(){
state.count++
}
decrement(){
state.count--
}
}
//parent.vue
<template>
<p>{{count}}p>
template>
<script>import{state}from'../store'exportdefault{computed:{
count(){returnstate.count
}
}
}script>
//child.vue
import{mutations}from'../store'
exportdefault{
methods:{
handleClick(){
mutations.increment()
}
}
}
children/root
通過給子元件定義 ref
屬性可以使用 $refs
來直接操作子元件的方法和屬性。
<childref="list">child>
比如子元件有一個 getList
方法,可以通過如下方式進行呼叫,實現父到子的通訊:
this.$refs.list.getList()
除了 $refs
外,其他3個都是自 Vue
例項建立後就會自動包含的屬性,使用和上面的類似。
6類可以掌握的修飾符
表單修飾符
表單類的修飾符都是和 v-model
搭配使用的,比如:v-model.lazy
、v-model-trim
以及 v-model.number
等。
.lazy
:對錶單輸入的結果進行延遲響應,通常和v-model
搭配使用。正常情況下在input
裡輸入內容會在p
標籤裡實時的展示出來,但是加上.lazy
後則需要在輸入框失去焦點的時候才觸發響應。<inputtype="text"v-model.lazy="name"/>
<p>{{name}}p>.trim
:過濾輸入內容的首尾空格,這個和直接拿到字串然後通過str.trim()
去除字串首尾空格是一個意思。.number
:如果輸入的第一個字元是數字,那就只能輸入數字,否則他輸入的就是普通字串。
事件修飾符
Vue
的事件修飾符是專門為 v-on
設計的,可以這樣使用:@click.stop="handleClick"
,還能串聯使用:@click.stop.prevent="handleClick"
。
<div@click="doDiv">
clickdiv
<p@click="doP">clickpp>
div>
.stop
:阻止事件冒泡,和原生event.stopPropagation()
是一樣的效果。如上程式碼,當點選p
標籤的時候,div
上的點選事件也會觸發,加上.stop
後事件就不會往父級傳遞,那父級的事件就不會觸發了。.prevent
:阻止預設事件,和原生的event.preventDefault()
是一樣的效果。比如一個帶有href
的連結上添加了點選事件,那麼事件觸發的時候也會觸發連結的跳轉,但是加上.prevent
後就不會觸發連結跳轉了。.capture
:預設的事件流是:捕獲階段-目標階段-冒泡階段,即事件從最具體目標元素開始觸發,然後往上冒泡。而加上.capture
後則是反過來,外層元素先觸發事件,然後往深層傳遞。.self
:只觸發自身的事件,不會傳遞到父級,和.stop
的作用有點類似。.once
:只會觸發一次該事件。.passive
:當頁面滾動的時候就會一直觸發onScroll
事件,這個其實是存在效能問題的,尤其是在移動端,當給他加上.passive
後觸發的就不會那麼頻繁了。.native
:現在在元件上使用v-on
只會監聽自定義事件 (元件用$emit
觸發的事件)。如果要監聽根元素的原生事件,可以使用.native
修飾符,比如如下的el-input
,如果不加.native
當回車的時候就不會觸發search
函式。<el-inputtype="text"v-model="name"@keyup.enter.native="search">el-input>
❝串聯使用事件修飾符的時候,需要注意其順序,同樣2個修飾符進行串聯使用,順序不同,結果大不一樣。
❞@click.prevent.self
會阻止所有的點選事件,而@click.self.prevent
只會阻止對自身元素的點選。
滑鼠按鈕修飾符
.left
:滑鼠左鍵點選;.right
:滑鼠右鍵點選;.middle
:滑鼠中鍵點選;
鍵盤按鍵修飾符
Vue
提供了一些常用的按鍵碼:
.enter
.tab
.delete
(捕獲“刪除”和“退格”鍵).esc
.space
.up
.down
.left
.right
另外,你也可以直接將 KeyboardEvent.key
暴露的任意有效按鍵名轉換為 kebab-case
來作為修飾符,比如可以通過如下的程式碼來檢視具體按鍵的鍵名是什麼:
<input@keyup="onKeyUp">
onKeyUp(event){
console.log(event.key)//比如鍵盤的方向鍵向下就是ArrowDown
}
.exact修飾符
.exact
修飾符允許你控制由精確的系統修飾符組合觸發的事件。
<buttonv-on:click.ctrl="onClick">Abutton>
<buttonv-on:click.ctrl.exact="onCtrlClick">Abutton>
<buttonv-on:click.exact="onClick">Abutton>
.sync修飾符
.sync
修飾符常被用於子元件更新父元件資料。直接看下面的程式碼:
//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 不能和表示式一起使用
❞
如果需要設定多個 prop
,比如:
<child:name.sync="name":age.sync="age":sex.sync="sex">child>
可以通過 v-bind.sync
簡寫成這樣:
<childv-bind.sync="person">child>
person:{
name:'bubuzou',
age:21,
sex:'male',
}
Vue
內部會自行進行解析把 person
物件裡的每個屬性都作為獨立的 prop
傳遞進去,各自新增用於更新的 v-on
監聽器。而從子元件進行更新的時候還是保持不變,比如:
this.$emit('update:name','hello')
6種方式編寫可複用模組
今天需求評審了一個需求,需要實現一個詳情頁,這個詳情頁普通使用者和管理員都能進去,但是展示的資料有稍有不同,但絕大部分是一樣的;最主要的區別是詳情對於普通使用者是純展示,而對於管理員要求能夠編輯,然後管理員還有一些別的按鈕許可權等。需求看到這裡,如果在排期的時候把使用者的詳情分給開發A做,而把管理員的詳情分給B去做,那這樣做的結果就是開發A寫了一個詳情頁,開發B寫了一個詳情頁,這在開發階段、提測後的修改 bug
階段以及後期迭代階段,都需要同時維護這 2 個檔案,浪費了時間浪費了人力,所以你可以從中意識到編寫可複用模組的重要性。
而 Vue
作者尤大為了讓開發者更好的編寫可複用模組,提供了很多的手段,比如:元件、自定義指令、渲染函式、外掛以及過濾器等。
元件
元件是 Vue
中最精髓的地方,也是我們平時編寫可複用模組最常用的手段,但是由於這塊內容篇幅很多,所以不在這裡展開,後續會寫相關的內容進行詳述。
使用混入mixins
什麼是混入呢?從程式碼結構上來看,混入其實就是半個元件,一個 Vue
元件可以包括 template
、script
和 style
三部分,而混入其實就是 script
裡面的內容。一個混入物件包含任意元件選項,比如 data
、methods
、computed
、watch
、生命週期鉤子函式、甚至是 mixins
自己等,混入被設計出來就是旨在提高程式碼的靈活性、可複用性。
什麼時候應該使用混入呢?當可複用邏輯只是 JS
程式碼層面的,而無 template
的時候就可以考慮用混入了。比如需要記錄使用者在頁面的停留的時間,那我們就可以把這段邏輯抽出來放在 mixins
裡:
//mixins.js
exportconststatMixin={
methods:{
enterPage(){},
leavePage(){},
},
mounted(){
this.enterPage()
},
beforeDestroyed(){
this.leavePage()
}
}
然後在需要統計頁面停留時間的地方加上:
import{statMixin}from'../common/mixins'
exportdefault{
mixins:[statMixin]
}
使用混入的時候要注意和元件選項的合併規則,可以分為如下三類:
data
將進行遞迴合併,對於鍵名衝突的以元件資料為準://mixinA的data
data(){
obj:{
name:'bubuzou',
},
}
//componentA
exportdefault{
mixins:[mixinA],
data(){
obj:{
name:'hello',
age:21
},
},
mounted(){
console.log(this.obj)//{name:'bubuzou','age':21}
}
}對於生命週期鉤子函式將會合併成一個數組,混入物件的鉤子將先被執行:
//mixinA
constmixinA={
created(){
console.log('第一個執行')
}
}
//mixinB
constmixinB={
mixins:[mixinA]
created(){
console.log('第二個執行')
}
}
//componentA
exportdefault{
mixins:[mixinB]
created(){
console.log('最後一個執行')
}
}值為物件的選項,例如
methods
、components
和directives
,將被合併為同一個物件。兩個物件鍵名衝突時,取元件物件的鍵值對。
自定義指令
除了 Vue
內建的一些指令比如 v-model
、v-if
等,Vue
還允許我們自定義指令。在 Vue2.0
中,程式碼複用和抽象的主要形式是元件。然而,有的情況下,你仍然需要對普通 DOM
元素進行底層操作,這時候就會用到自定義指令。比如我們可以通過自定義一個指令來控制按鈕的許可權。我們期望設計一個如下形式的指令來控制按鈕許可權:
<buttonv-auth="['user']">提交button>
通過在按鈕的指令裡傳入一組許可權,如果該按鈕只有 admin
許可權才可以提交,而我們傳入一個別的許可權,比如 user
,那這個按鈕就不應該顯示了。接下來我們去註冊一個全域性的指令:
//auth.js
constAUTH_LIST=['admin']
functioncheckAuth(auths){
returnAUTH_LIST.some(item=>auths.includes(item))
}
functioninstall(Vue,options={}){
Vue.directive('auth',{
inserted(el,binding){
if(!checkAuth(binding.value)){
el.parentNode&&el.parentNode.removeChild(el)
}
}
})
}
exportdefault{install}
然後我們需要在 main.js
裡通過安裝外掛的方式來啟用這個指令:
importAuthfrom'./utils/auth'
Vue.use(Auth)
使用渲染函式
這裡將使用渲染函式實現上面介紹過的的許可權按鈕。使用方式如下,把需要控制權限的按鈕包在許可權元件 authority
裡面,如果有該許可權就顯示,沒有就不顯示。
<authority:auth="['admin']">
<button>提交button>
authority>
然後我們用渲染函式去實現一個 authority
元件: