34條我能告訴你的Vue之實操篇

這是我學習整理的關於 Vue.js 系列文章的第一篇,另外還有兩篇分別是關於優化和原理的。但願讀完這3篇文章,你能對 Vue 有個更深刻的認識。javascript

7種組件通訊方式隨你選

組件通訊是 Vue 的核心知識,掌握這幾個知識點,面試開發一點問題都沒有。css

props/@on+$emit

用於實現父子組件間通訊。經過 props 能夠把父組件的消息傳遞給子組件:html

<!-- parent.vue -->
<child :title="title"></child>
複製代碼
// child.vue
props: {
    title: {
        type: String,
        default: '',
    }
}
複製代碼

這樣一來 this.title 就直接拿到從父組件中傳過來的 title 的值了。注意,你不該該在子組件內部直接改變 prop,這裏就很少贅述,能夠直接看官網介紹前端

而經過 @on+$emit 組合能夠實現子組件給父組件傳遞信息:vue

<!-- parent.vue -->
<child @changeTitle="changeTitle"></child>
複製代碼
// child.vue
this.$emit('changeTitle', 'bubuzou.com')
複製代碼

a t t r s attrs和 listeners

Vue_2.4 中新增的 $attrs/$listeners 能夠進行跨級的組件通訊。 $attrs 包含了父級做用域中不做爲 prop 的屬性綁定(classstyle 除外),好像聽起來有些很差理解?沒事,看下代碼就知道是什麼意思了:java

<!-- 父組件 index.vue -->
<list class="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 中加一個子組件:node

<!-- 子組件 list.vue -->
<detail v-bind="$attrs"></detial>
複製代碼
// 孫子組件 detail.vue
// 不定義props,直接打印 $attrs
mounted() {
    console.log(this.$attrs)  // {title: "標題", desc: "描述"}
}
複製代碼

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

$attrs 是能夠進行跨級的參數傳遞,實現父到子的通訊;一樣的,經過 $listeners 用相似的操做方式能夠進行跨級的事件傳遞,實現子到父的通訊。$listeners 包含了父做用域中不含 .native 修飾的 v-on 事件監聽器,經過 v-on="$listeners" 傳遞到子組件內部。git

<!-- 父組件 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 組合以容許一個祖先組件向其全部子孫後代注入一個依賴,能夠注入屬性和方法,從而實現跨級父子組件通訊。在開發高階組件和組件庫的時候尤爲好用。github

// 父組件 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('改變後的值')  // 執行這句後將打印:改變後的值 
}
複製代碼

provideinject 的綁定對於原始類型來講並非可響應的。這是刻意爲之的。然而,若是你傳入了一個可監聽的對象,那麼其對象的 property 仍是可響應的。這也就是爲何在孫子組件中改變了 title,可是父組件不會從新渲染的緣由。

EventBus

以上三種方式都是隻能從父到子方向或者子到父方向進行組件的通訊,而我就比較牛逼了😀,我還能進行兄弟組件之間的通訊,甚至任意2個組件間通訊。利用 Vue 實例實現一個 EventBus 進行信息的發佈和訂閱,能夠實如今任意2個組件之間通訊。有兩種寫法均可以初始化一個 eventBus 對象:

  1. 經過導出一個 Vue 實例,而後再須要的地方引入:

    // eventBus.js
    import Vue from 'vue'
    export const EventBus = new Vue()
    複製代碼

    使用 EventBus 訂閱和發佈消息:

    import {EventBus} from '../utils/eventBus.js'
    
    // 訂閱處
    EventBus.$on('update', val => {})
    
    // 發佈處
    EventBus.$emit('update', '更新信息')
    複製代碼
  2. main.js 中初始化一個全局的事件總線:

    // main.js
    Vue.prototype.$eventBus = new Vue()
    複製代碼

    使用:

    // 須要訂閱的地方
    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() {
        return this.$store.state.count
    },
    ...mapState({
        count: state => state.count
    }),
    ...mapState(['count']),
},
複製代碼

又好比 mapMutations, 如下兩種函數的定義方式要實現的功能是同樣的,都是要提交一個 mutation 去改變 state 中的數據:

methods: {
    increment() {
        this.$store.commit('increment')
    },
    ...mapMutations(['increment']),
}
複製代碼

接下來就用一個極簡的例子來展現 Vuex 中任意2個組件間的狀態管理。 一、 新建 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--
        }
    },
})
複製代碼

二、 建立一個帶 storeVue 實例

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')
複製代碼

三、 任意組件 A 實現點擊遞增

<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>
複製代碼

四、 任意組件 B 實現點擊遞減

<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 配置去實現組件通訊,固然真實項目中的配置確定會更復雜,好比須要對 State 數據進行二次篩選會用到 Getter,而後若是須要異步的提交那麼須要使用 Action,再好比若是模塊不少,能夠將 store 分模塊進行狀態管理。對於 Vuex 更多複雜的操做仍是建議去看Vuex 官方文檔,而後多寫例子。

Vue.observable實現mini vuex

這是一個 Vue2.6 中新增的 API,用來讓一個對象能夠響應。咱們能夠利用這個特色來實現一個小型的狀態管理器。

// 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()
        }
    }
}
複製代碼

r e f s / refs/ children/ p a r e n t / parent/ root

經過給子組件定義 ref 屬性可使用 $refs 來直接操做子組件的方法和屬性。

<child ref="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 後則須要在輸入框失去焦點的時候才觸發響應。

    <input type="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">
    click div
    <p @click="doP">click p</p>
</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-input type="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 修飾符容許你控制由精確的系統修飾符組合觸發的事件。

<!-- 即便 Alt 或 Shift 被一同按下時也會觸發 -->
<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的時候才觸發 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 沒有任何系統修飾符被按下的時候才觸發 -->
<button v-on:click.exact="onClick">A</button>
複製代碼

.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 簡寫成這樣:

<child v-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
export const statMixin = {
    methods: {
        enterPage() {},
        leavePage() {},
    },
    mounted() {
        this.enterPage()
    },
    beforeDestroyed() {
        this.leavePage()
    }
}

複製代碼

而後在須要統計頁面停留時間的地方加上:

import { statMixin } from '../common/mixins'
export default {
    mixins: [statMixin]
}
複製代碼

使用混入的時候要注意和組件選項的合併規則,能夠分爲以下三類:

  • data 將進行遞歸合併,對於鍵名衝突的以組件數據爲準:

    // mixinA 的 data
    data() {
        obj: {
            name: 'hello',
        },
    }
    
    // component A
    export default {
        mixins: [mixinA],
        data(){
            obj: {
                name: 'bubuzou',
                age: 21
            },
        },
        mounted() {
            console.log( this.obj )  // { name: 'bubuzou', 'age': 21 }
        }
    }
    複製代碼
  • 對於生命週期鉤子函數將會合併成一個數組,混入對象的鉤子將先被執行:

    // mixin A
    const mixinA = {
        created() {
            console.log( '第一個執行' )
        }
    }
    
    // mixin B
    const mixinB = {
        mixins: [mixinA]
        created() {
            console.log( '第二個執行' )
        }
    }
    
    // component A
    export default {
        mixins: [mixinB]
        created() {
            console.log( '最後一個執行' )
        }
    }
    複製代碼
  • 值爲對象的選項,例如 methodscomponentsdirectives,將被合併爲同一個對象。兩個對象鍵名衝突時,取組件對象的鍵值對。

自定義指令

除了 Vue 內置的一些指令好比 v-modelv-if 等,Vue 還容許咱們自定義指令。在 Vue2.0 中,代碼複用和抽象的主要形式是組件。然而,有的狀況下,你仍然須要對普通 DOM 元素進行底層操做,這時候就會用到自定義指令。好比咱們能夠經過自定義一個指令來控制按鈕的權限。咱們指望設計一個以下形式的指令來控制按鈕權限:

<button v-auth="['user']">提交</button>
複製代碼

經過在按鈕的指令裏傳入一組權限,若是該按鈕只有 admin 權限才能夠提交,而咱們傳入一個別的權限,好比 user,那這個按鈕就不該該顯示了。 接下來咱們去註冊一個全局的指令:

// 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)
複製代碼

使用渲染函數

這裏將使用渲染函數實現上面介紹過的的權限按鈕。 使用方式以下,把須要控制權限的按鈕包在權限組件 authority 裏面,若是有該權限就顯示,沒有就不顯示。

<authority :auth="['admin']">
    <button>提交</button>
</authority>
複製代碼

而後咱們用渲染函數去實現一個 authority 組件:

<script>
const AUTH_LIST = ['admin', 'user', 'org']

function checkAuth(auths) {
    return AUTH_LIST.some(item => auths.includes(item))
}
export default {
    functional: true,
    props: {
        auth: {
            type: Array,
            required: true
        }
    },
    render(h, context) {
        const { props,  scopedSlots} = context
        return checkAuth(props.auth) ? scopedSlots.default() : null
    }
}
</script>
複製代碼

全局註冊這個組件:

// main.js
import Authority from './components/authority'
Vue.component('authority', Authority)
複製代碼

使用過濾器

Vue 提供了自定義過濾器的功能,主要應用場景是想要將數據以某種格式展現出來,而原始數據又不符合這種格式的時候。好比有一組關於人的數據,以下:

[{
    name: '張茂',
    population: 'young',
}, {
    name: '王麗',
    population: 'middle',
}, {
    name: '郝鵬程',
    population: 'child',
}]
複製代碼

其中有一項是關於按照年齡劃分的羣體類型 population,而它是用 code 進行標識的,咱們但願在展現的時候可以顯示成對應的中文意思,好比 young 顯示成青年。那咱們就能夠定義一個以下的局部過濾器:

export default {
    filters: {
        popuFilters(value) {
            if (!value) { return '未知' }
            let index = ['child', 'lad', 'young', 'middle', 'wrinkly'].indexOf(value)
            return index > 0 && ['兒童', '少年', '青年', '中年', '老年'][index] || '未知'
        }
    }
}
複製代碼

使用過濾器的時候只要在 template 中這樣使用便可:

<p>{{ item.population | popuFilters }}</p>
複製代碼

自定義插件

在某些狀況下,咱們封裝的內容可能不須要使用者對其內部代碼結構進行了解,其只須要熟悉咱們提供出來的相應方法和 api 便可,這須要咱們更系統性的將公用部分邏輯封裝成插件,來爲項目添加全局功能,好比常見的 loading 功能、彈框功能等。

開發 Vue 的插件應該暴露一個 install 方法。這個方法的第一個參數是 Vue 構造器,第二個參數是一個可選的選項對象。能夠經過以下4種方式來自定義插件:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 邏輯...
  }

  // 2. 添加全局資源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 邏輯...
    }
    ...
  })

  // 3. 注入組件選項
  Vue.mixin({
    created: function () {
      // 邏輯...
    }
    ...
  })

  // 4. 添加實例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 邏輯...
  }
}
複製代碼

而後須要在入口文件,好比 main.js 中註冊插件:

import MyPlugin from './plugins/plugins.js'
Vue.use(MyPlugin)
複製代碼

3種方式手寫優雅代碼

平時寫項目的時候咱們都是在第一時間完成需求功能的開發、提測修改 bug 等,而後開開心心的等待着發佈生產覺得沒啥事情了。其實回過頭來細細的看咱們平時寫的代碼,可能會發現不少地方都是值得優化的,好比對於不少重複性很強的代碼,好比對於某些寫得很繁雜的地方。優雅的代碼能夠化機械爲自動、化繁爲簡,看人開了如沐春風,心情大好。這裏列了幾個在 Vue 中必定會遇到的問題,而後經過優雅的方式進行解決。

自動化導入模塊

在開發一個稍微大點的項目的時候,會習慣將路由按照模塊來劃分,而後就可能會出現以下這種代碼:

// router.js
import Vue from 'vue'
import Router from 'vue-router'
// 導入了一大堆路由文件
import mediator from './mediator'
import judges from './judges'
import disputeMediation from './disputeMediation'
import onlineMediation from './onlineMediation'
import useraction from './useraction'
import organcenter from './organcenter'
import admin from './admin'

let routeList = []
routeList.push(mediator, judges, disputeMediation, onlineMediation, useraction, organcenter, admin)

export default new Router({
    mode: 'history',
    routes: routeList,
})
複製代碼

其實真實的遠遠不止這麼點,就我本地項目而言就有20幾個路由文件,寫了一大堆的導入代碼,顯得很臃腫,更無奈的是每當須要新增一個路由模塊,還得再次 import 再次 push,那麼有沒有什麼辦法能夠解決這個問題呢?答案天然是有的。

利用 webpackrequire.context 就能夠很優雅的解決這個問題,使用語法以下:

require.context(
    directory,  // 搜索的目錄
    useSubdirectories = true,  // 是否搜索子目錄
    regExp = /^\.\/.*$/,  // 匹配的目標文件格式
    mode = 'sync'  // 同步仍是異步
)
複製代碼

有了這個語法,咱們就能很容易的寫出下面的代碼:

import Vue from 'vue'
import Router from 'vue-router'

let routeList = []
let importAll = require.context('@/publicResource/router', false, /\.js$/)
importAll.keys().map(path => {
    // 由於 index.js 也在 @/publicResource/router 目錄下,因此須要排除
    if (!path.includes('index.js')) {
        //兼容處理:.default 獲取 ES6 規範暴露的內容; 後者獲取 commonJS 規範暴露的內容
        let router = importAll(path).default || importAll(path)
        routeList(router)
    }
})
 
export default new Router({
    mode: 'history',
    routes: routeList,
})
複製代碼

其實不只僅只是用在導入路由模塊這裏,對於項目裏任何須要導入大量本地模塊的地方均可以使用這種方式來解決。

模塊化註冊插件

相信寫 Vue 的同窗們都知道 element-ui 這個組件庫,在使用這個組件庫的時候大部分都是隻使用某些個別的組件,因此基本上都是按需引入須要的組件,而後就有以下一堆 Vue.use() 的代碼:

// main.js
import Vue from 'vue'
import {
    Input,
    Radio,
    RadioGroup,
    Checkbox,
    CheckboxGroup,
    Select
    // 還有不少組件
} from 'element-ui'

Vue.use(Input)
Vue.use(Radio)
Vue.use(RadioGroup)
Vue.use(Checkbox)
Vue.use(CheckboxGroup)
Vue.use(Select)
複製代碼

這樣寫是沒任何問題的,就是看着不夠簡潔舒服,那更優雅的作法是把這塊邏輯抽到一個文件裏,而後經過註冊插件的方式來使用他們:

// elementComponent.js
import {
    Input,
    Radio,
    RadioGroup,
    Checkbox,
    CheckboxGroup,
    Select
    // 還有不少組件
} from 'element-ui'

const components = {
    Input,
    Radio,
    RadioGroup,
    Checkbox,
    CheckboxGroup,
    Select
}
function install(Vue){
    Object.keys(components).forEach(key => Vue.use(components[key]))
}
export default { install }
複製代碼

而後在 main.js 裏使用這個插件:

// main.js
import Vue from 'vue'
import elementComponent from './config/elementComponent'
Vue.use(elementComponent)
複製代碼

優雅導出請求接口

不知道大夥是如何定義請求接口的,就我目前這個項目而言,是這麼作的:

// api.js
import http from './config/httpServer.js'

 /* 登入頁面獲取公鑰 */
export const getPublicKey = (data) => {
    return http({ url: '/userGateway/user/getPublicKey' }, data)
}

// 用戶登陸
export const login = data => {
    return http({ url: '/userGateway/userSentry/login' }, data)
}

// 驗證碼登陸
export const loginByCode = data => {
    return http({ url: '/userGateway/userSentry/loginByCode' }, data)
}
複製代碼

在組件中使用接口:

<script>
import { getPublicKey } from './config/api.js'
export default {
    mounted() {
        getPublicKey().then(res => {
            // xxx
        }).catch(err => {
            // xxx
        })
    }
}
</script>
複製代碼

這一切都很正常,但,咱們這個項目總共有200多個接口,按照上面這種定義方式的話,一個接口定義加上空行須要佔用 5 行,因此若是把所有接口都定義到這個 api.js 裏須要佔用 1000 行左右,看了實在讓人心很慌呀。因此以爲應該這個地方應該能夠優化一下。

/userGateway/user/getPublicKey
複製代碼

上面這是一個後端給接口路徑,斜杆把這個路徑劃分紅 3 個子串,而最後一個子串一定是惟一的,因此咱們能夠從中作文章。因而乎就有了下面的代碼:

// api.js
const apiList = [
    '/userGateway/user/getPublicKey',  // 登入頁面獲取公鑰
    '/userGateway/userSentry/login',  // 用戶登陸
    '/userGateway/userSentry/loginByCode',  // 驗證碼登陸
]

let apiName, API = {}
apiList.forEach(path => {
    // 使用正則取到接口路徑的最後一個子串,好比: getPublicKey
    apiName = /(?<=\/)[^/]+$/.exec(path)[0]
    API[apiName] = (data) => {
        return http({url: path}, data)
    }
})
export { API }
複製代碼

這樣大概就把定義一個接口須要佔用 5 行縮小到只須要 1 行了,大大減少了文件內容。在瀏覽這個文件的時候,個人鼠標滾輪也不會一直在滾滾滾了。

若是是這樣定義接口的話,那在使用的時候還須要作點變化的:

<script>
import { API } from './config/api.js'
export default {
    mounted() {
        API.getPublicKey().then(res => {
            // xxx
        }).catch(err => {
            // xxx
        })
    }
}
</script>
複製代碼

4種$event傳參方式

在進行實際項目開發的時候常常會須要經過事件傳遞參數,這裏總結了4種應用場景。

用於組件通訊

好比子組件經過 $emit 來調用父組件方法的時候,能夠在父組件中用 $event 接收到從子組件傳遞過來的參數:

<!-- 子組件 -->
<button @click="$emit('changeText', '18px')">點擊加大字號</button>
複製代碼
<!-- 父組件 -->
<blog-post @changeText="changeText('article', $event)"></blog-post>
複製代碼
changeText(type, value) {
    console.log(type, value)  // 'article' '18px'
}
複製代碼

若是子組件傳遞過來的參數有多個,這個時候用 $event 就不太行了,此時能夠用 arguments 代替:

<!-- 子組件 -->
<button @click="$emit('changeText', 'red', '18px')">點擊改變樣式</button>
複製代碼
<!-- 父組件 -->
<blog-post @changeText="changeText(...arguments, 'article')"></blog-post>
複製代碼
changeText(...value) {
    console.log( value )  // ['red', '18px', 'article']
}
複製代碼

傳遞原生DOM事件對象

好比咱們須要獲取到當前的點擊元素,就能夠經過給點擊事件傳遞 $event 參數:

<button @click="submit('first', $event)">提交</button>
複製代碼
submit(type, event) {
    const target = event.target.tagName
}
複製代碼

用於第三方類庫事件回調

好比有一個組件裏使用了好幾個 element-ui 的分頁組件,每一個分頁都有一個 current-change 事件,用來處理當分頁改變以後的事情,這樣的話咱們就須要寫多個回調函數,可是若是用如下方式,咱們就也能夠只寫一個函數,經過 type 來判斷是哪一個分頁的回調,而 $event 則用來傳遞 current-change 回調默認的參數:

<!-- 頁面列表的分頁 -->
<el-pagination @current-change="changePage('main', $event)">
</el-pagination>

<!-- 彈窗A列表的分頁 -->
<el-pagination @current-change="changePage('modalA', $event)">
</el-pagination>

<!-- 彈窗B列表的分頁 -->
<el-pagination @current-change="changePage('modalB', $event)">
</el-pagination>
複製代碼
changePage(type, page) {
    const types = ['main', 'modalA', 'modalB']
    types[type] && (this[types[type]].pageIndex = page) && this.getList(type)
}
複製代碼

使用箭頭函數處理

對於第三種場景,使用第三方類庫組件的時候,須要給事件回調增長額外的參數,若是默認的回調參數只有1個那麼咱們就可使用上面的那種方式,可是若是回調參數有多個的話,用 $event 就很差處理了,可使用箭頭函數。好比文件上傳的時候,有個 on-change 屬性,當文件變化的時候就會觸發回調,正常狀況下咱們這樣寫是沒問題的:

<el-upload :on-change="changeFile">
    <el-button>上傳</el-button>
</el-upload>
複製代碼
changeFile(file, fileList) {}
複製代碼

可是若是一個組件裏有多個文件上傳,而咱們又不想寫多個 changeFile,那就須要傳遞額外的參數 type 了 :

<el-upload :on-change="(file, fileList) => changeFile('org', file, fileList)">
    <el-button>上傳</el-button>
</el-upload>
複製代碼
changeFile(type, file, fileList) {}
複製代碼

3種深刻watch的用法

當即執行

watchVue 中的偵聽器,能夠偵聽一個 Vue 實例上的數據,當數據變更的時候,就會觸發該偵聽器。因此他的應用場景就是:當某個數據變更後須要作什麼的時候就可使用 watch 啦。 對於 watch,日常咱們寫得最多的估計是以下這種寫法:

watch: {
    list: function(val) {
        this.getMsg()
    }
}
複製代碼

若是咱們但願組件初始化的時候就執行一次 getMsg 方法,能夠直接在 mounted 裏調用:

mounted() {
    this.getMsg()
}
複製代碼

其實,還有一種更加簡便的寫法,經過給 watch 設置 immediate: true ,便可:

watch: {
    list: {
        handler(val) {  // 注意別寫錯成 handle
            this.getMsg()
        },
        immediate: true
    }
}
複製代碼

深度監聽

偵聽器對於屬性變動後會自動調用一次,可是僅限於該屬性自己,若是變動的是屬性的屬性,則不會觸發偵聽回調,若是想要實現這個功能能夠給 watch 加上 'deep: true' 便可:

watch: {
    obj: {
        handler(val) { // do something },
        deep: true
    }
},
mounted() {
    this.obj.name = 'bubuzou'  // 將觸發 handler
}
複製代碼

多個handlers

實際上,watch 能夠設置爲數組,支持類型爲 StringObjectFunction。觸發後,多個處理函數都將被調用。

watch: {
    obj: [
        'print',
        {
            handler: 'print',
            deep: true
        },
        function(val, oldValue) {
            console.log(val)
        }
    ]
},
methods: {
    print() {
        console.log(this.obj)
    }
}
複製代碼

5個其餘開發小技巧

掌握 Vue 的開發小技巧,在一些特定的場景下真的很管用,這裏列了一些經常使用的小技巧。

函數式組件實現零時變量

咱們在使用插槽的時候,知道有一個叫作插槽 prop 的知識,今天咱們用他和函數式組件結合在一塊,實現一個零時變量的組件:

// tempvar.vue
<script>
export default {
    functional: true,
    render(h, context) {
        const { props,  scopedSlots} = context
        return scopedSlots.default && scopedSlots.default(props || {})
    }
}
</script>
複製代碼

定義好了函數式組件,咱們就能夠在須要的地方引入且使用他:

<template>
<tempvar :var1="`hello ${user.name}`" :var2="user.age ? user.age : '18'">
    <template v-slot="{var1, var2}">
       姓名: {{ var1 }}
       年齡:{{ var2 }}
    </template>
</tempvar>
</template>
<script> import tempvar from '@/components/tempvar.vue' export default { data() { return { user: { name: 'bubuzou', age: 12, }, } } components: { tempvar } } </script>
複製代碼

可能細心的小夥伴發現了,要把名字前加個 hello、默認年齡設置爲 18 用計算屬性就能夠了呀?爲啥還要搞那麼複雜,專門用一個函數式組件去實現呢?其實這個小技巧仍是頗有必要存在的,當許多組件都有這種數據的從新計算的時候,若是沒有使用這個技巧,那麼就須要寫不少不少的計算屬性,而有了函數式組件 tempvar 後,只須要在組件裏引入他,而後寫插槽就行了。就至關於把寫計算屬性的功夫花在了寫插槽上了。總而言之,兩種方式均可以實現相似的屬性計算功能,該怎麼選,隨你喜歡啦。

調試template(不推薦)

在開發調試的時候常常會須要經過 console.log 來打印出某個數據對象來查看其內部的結構或者字段值,可是這樣作確定沒必要在 template 裏將其輸出更直接。好比有這樣一個數據:

obj: {
    name: 'bubuzou',
    age: 21,
}
複製代碼

在模板中展現:

<p>{{ obj }}</p>
複製代碼

頁面渲染完成後會看到:

{ "name": "bubuzou", "age": 21 }
複製代碼

對於這樣的渲染結果雖然沒什麼問題,可是若是這個 obj 是層級很深且字段不少的數據,顯示出來就會一堆數據砸在一塊,絲毫沒有閱讀體驗。

所以基於這個背景,咱們能夠將 console.log 掛載在 Vue 的實例原型上:

// main.js
Vue.prototype.$log = window.console.log
複製代碼

而後就能夠開開心心在模板中使用他了:

<p>{{ $log( obj ) }}</p>
複製代碼

這樣會在瀏覽器控制檯輸出當前的數據對象,在顯示效果上和 console.log 直接打印別無二致。

但說了這麼多,使用 Vue 進行開發調試仍是強烈推薦官方的vue-devtools 工具,誰用誰知道。

監聽子組件的鉤子函數

一般若是咱們想在子組件鉤子函數觸發的時候通知父組件,咱們能夠這樣作:

<!-- parent.vue -->
<child @mounted="doSomething"></child>
複製代碼
// child.vue
this.$emit('mounted')
複製代碼

其實還有一種更加簡單的寫法,那就是使用 hookEvent

<child @hook:mounted="doSomething"></child>
複製代碼

鉤子函數除了以上用法,還能夠經過動態註冊作一些別的事情,好比組件銷燬前進行資源的釋放:

mounted() {
    let setIntervalId = setInterval(() => {
        console.log(888);
    }, 1000)

    this.$once("hook:beforeDestroy", () => {
        clearInterval(setIntervalId)
        setIntervalId = null
    })
}
複製代碼

路由參數解耦

參數解耦,啥意思呢?彆着急,咱們先來看好比對於這麼一串路由:

const router = [{
    path: '/home/:type/:id',
    name: 'Home',
    component: Home,
}]
複製代碼

當前頁面的路徑是 http://xxx/detail/preview/21?sex=male,平時咱們寫代碼的時候或多或少的會寫出這種代碼,在組件裏使用 $route 給組件傳參數:

mounted() {
    if (this.$route.params.type === 'preview') {
        this.isPreview = true
    } else {
        this.isPreview = false
    }
    this.id = this.$route.params.id
    this.sex = this.$route.query.sex
}
複製代碼

這樣子寫自己沒什麼問題,就是會使得組件和路由高度耦合,讓組件只能在含有特定 URL 的頁面中使用,限制了組件的通用性。其實,咱們能夠經過 props 傳參,來解耦路由參數,將上面的路由配置改爲以下:

const router = [{
    path: '/home/:type/:id',
    name: 'Home',
    component: Home,
    props: (route) => ({
        type: route.params.type,
        id: route.params.id,
        sex: route.query.sex,
    })
}]
複製代碼

而後在組件 props 加上參數:

props: ['type', 'id', 'sex']
複製代碼

組件裏使用參數的時候就不須要用 this.$route,而是能夠直接 this.type 便可。這樣一來,這個組件就能夠在任何地方使用了。

深度做用選擇器

當給 style 加上 scoped,頁面渲染完成後會給 htmlcss 選擇器加上哈希值用於表示惟一性:

<div class="home" data-v-fae5bece>
    <button data-v-fae5bece class="el-button el-button-primary">提交</button>
</div>
複製代碼
.home .el-button[data-v-fae5bece] {
    font-size: 20px;
}
複製代碼

對於在 style 中被加了 scoped 的組件,其樣式將只能做用於組件內部,不會對其子組件形成影響。好比有這樣一個組件:

<!-- 父組件 -->
<div class="home">
    <el-button type="primary">父按鈕</button>
    <child></child>
</div>

<style lang="scss" scoped> .home .el-button { font-size: 20px; } </style>
複製代碼
<!-- 子組件 -->
<div class="child">
    <el-button type="primary">子按鈕</button>
</div>
複製代碼

當頁面渲染出來後,會是以下結果:

<div class="home" data-v-fae5bece>
    <button data-v-fae5bece class="el-button el-button-primary">父按鈕</button>
    <div class="child" data-v-fae5bece>
        <button class="el-button el-button-primary">子按鈕</button>
    </div>
</div>
複製代碼

根據上面的 html,咱們能夠看到 .home .el-button[data-v-fae5bece] 這個選擇器做用不到子按鈕這個 button

在實際項目中,咱們有時候須要讓父組件的樣式能做用到子組件,即便父組件的 style 上加了 scoped,那這個時候就須要用到深度做用選擇器 >>>,好比在剛剛的例子上能夠給父組件樣式加上深度做用選擇器。

深度做用選擇器會被 Vue Loader 處理,且只能在有預處理器的地方使用。因爲某些預處理器好比 Sass 不能正確解析 >>>,因此咱們可使用它的別名:/deep/::v-deep 來替代。

<style lang="scss" scoped>
.home {
    /deep/ .el-button {
        font-size: 20px;
    }
}
</style>
複製代碼

加上深度做用選擇器後,選擇器會由原來的:

.home .el-button[data-v-fae5bece] {}
複製代碼

變成以下的:

.home[data-v-fae5bece] .el-button {}
複製代碼

參考文章

感謝閱讀

若是本文對你有幫助的話,給本文點個贊吧 看得不過癮?這裏列一些以前的文章:

大海我來了

相關文章
相關標籤/搜索