1. 程式人生 > 其它 >vue 點選事件傳遞多個引數_不容錯過34條 Vue 高質量實戰技巧

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

的屬性繫結(classstyle 除外),好像聽起來有些不好理解?沒事,看下程式碼就知道是什麼意思了:

//父元件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

傳遞了4個引數,但是在子元件內部 props 裡只定義了一個 list,那麼此時 this.$attrs 的值是什麼呢?首先要去除 props 中已經綁定了的,然後再去除 classstyle,最後剩下 titledesc 結果和列印的是一致的。基於上面程式碼的基礎上,我們在給 list.vue 中加一個子元件:

// 子元件 list.vue
//孫子元件detail.vue
//不定義props,直接列印$attrs
mounted(){
console.log(this.$attrs)//{title:"標題",desc:"描述"}
}

在子元件中我們定義了一個 v-bind="$attrs" 可以把父級傳過來的引數,去除 propsclassstyle 之後剩下的繼續往下級傳遞,這樣就實現了跨級的元件通訊。

$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('改變後的值')//執行這句後將列印:改變後的值
}

provideinject 的繫結對於原始型別來說並不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的物件,那麼其物件的 property 還是可響應的。這也就是為什麼在孫子元件中改變了 title,但是父元件不會重新渲染的原因。

EventBus

以上三種方式都是隻能從父到子方向或者子到父方向進行元件的通訊,而我就比較牛逼了?,我還能進行兄弟元件之間的通訊,甚至任意2個元件間通訊。利用 Vue 例項實現一個 EventBus 進行資訊的釋出和訂閱,可以實現在任意2個元件之間通訊。有兩種寫法都可以初始化一個 eventBus 物件:

  1. 通過匯出一個 Vue 例項,然後再需要的地方引入:

    //eventBus.js
    importVuefrom'vue'
    exportconstEventBus=newVue()

    使用 EventBus 訂閱和釋出訊息:

    import{EventBus}from'../utils/eventBus.js'

    //訂閱處
    EventBus.$on('update',val=>{})

    //釋出處
    EventBus.$emit('update','更新資訊')
  2. 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、 建立一個帶 storeVue 例項

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.lazyv-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 的形式進行更新父元件中聲明瞭 .syncprop。上面父元件中的寫法其實是下面這種寫法的簡寫:

<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 元件可以包括 templatescriptstyle 三部分,而混入其實就是 script 裡面的內容。一個混入物件包含任意元件選項,比如 datamethodscomputedwatch 、生命週期鉤子函式、甚至是 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('最後一個執行')
    }
    }
  • 值為物件的選項,例如 methodscomponentsdirectives,將被合併為同一個物件。兩個物件鍵名衝突時,取元件物件的鍵值對。

自定義指令

除了 Vue 內建的一些指令比如 v-modelv-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 元件: