iT邦幫忙

2021 iThome 鐵人賽

DAY 28
1
Modern Web

不只懂 Vue 語法:Vue.js 觀念篇系列 第 28

不只懂 Vue 語法:試說明有哪些方式可以全域註冊方法?

問題回答

全域註冊的方法,意思是每個 Vue 元件都能使用的方法。在 Vue 2 會有以下方法:

  • Vue.prototype
  • plugin
  • mixin

Vue 3 同樣有以上方法,但注意是語法會有點不同。主要是不再在 Vue 的原型上建立,而是在 app 上建立,因此,你會發現以前的 Vue.mixin() 現在改為 app.mixin(),或者 Vue.use() 改為 app.use()等等。另外,Vue 3 也新增了 provideinject,作用可取代 app.config.globalProperties,即是 Vue 2 的 Vue.prototype

這不是常見面試題,但想藉此整理自己對全域註冊方法的理解,因此以下會再詳細說明這些語法和使用情景。

Vue.prototype

避免直接在全域建立變數或函式,我們可以在 Vue 的原型上註冊,並在每個 Vue 實例裏使用。如果幾乎每個元件都會用到此方法的話,此手法會比 mixin 方便,因為不用寫 import xxx from ... 然後又寫一行 mixins: [...]

看看 Vue 官方例子:

main.js

import Vue from 'vue'
import App from './App.vue'
...

// 定義全域變數
Vue.prototype.$username = 'Alysa'

// 定義全域函式
Vue.prototype.$reverseText = function (propertyName) {
  this[propertyName] = this[propertyName]
    .split('')
    .reverse()
    .join('')
}

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

在元件裏使用:

export default {
  data() {
    return {
      // 使用全域變數
      username: this.$username
    }
  },
  created() {
    // 使用全域函式
    this.$reverseText('username')
    console.log(this.username) // asylA
  }
}

要注意:

  • 不能使用箭頭函式來註冊函式,否則會出現 undefined 的錯誤。
  • Vue 官方表示慣常使用 $ 符號代表全域變數或方法。

稍為解釋第一點,因為 this 需要指向 Vue 實體,否則取不到資料。箭頭函式會繼承上一層函式所指向的 this。相反,傳統函式的 this 會取決於呼叫的物件,因此當我們在 created 裏呼叫 this.$reverseText$reverseText 裏的 this 就會指向 Vue 實體。

官方還有舉例一個實際常見用法:在全域註冊 axios 方法。

main.js

import axios from 'axios'
Vue.prototype.$http = axios

再在某元件使用:

created() {
    this.$http('https://randomuser.me/api/')
    .then( res => console.log(res.data)) // {results:...}
}

這樣每次開發時就不用下載 vue-axios 這種插件了,反正自己寫幾行就搞定了。

缺點:難以得知這是自行定義的方法

使用 Vue.prototype 的缺點是,當你是團隊開發,可能有些開發者不太熟 Vue,以為 this.$xxx 全都是 Vue 本身的方法,例如是 this.$http 這樣的寫法,會讓人誤會是 Vue 本身的方法。

plugin

除了載入第三方插件,我們也可以建立自己的 plugin。有幾點要注意:

  • 要在 new Vue ({}) 前使用 Vue.use() 載入 plugin。
  • Vue 會禁止你建立重複的 plugin。

使用 Vue CLI 時,我之前在這個教學影片學習過的做法是,先建立一個叫 libs 的資料夾,裏面再開一個資料夾 MyPlugin,並且建立 index.js 檔案。

src/ libs/ MyPlugin/ index.js

let MyPlugin = {}

// 需要使用 install 方法
MyPlugin.install = function(Vue, options) {

    // 注意!不能在 new Vue() 後使用這些方法
    Vue.greeting = function () {
        console.log('來自 plugin 的 message')
    }

    // 建立指令
    Vue.directive('greeting', {
        bind(el, binding, vnode, oldVnode) {
            console.log('來自 plugin 的 directive')
        }
    })

    // 建立 mixin
    Vue.mixin({
        created() {
            console.log('來自 plugin 的 mixin')
        }
    })

    // 使用 Vue prototype
    Vue.prototype.$greeting = function() {
        console.log('來自 plugin 的 $greeting 方法')
    }
}

export default MyPlugin

再在 main.js 引入此 plugin:

import MyPlugin from '@/libs/MyPlugin'

Vue.use(MyPlugin)

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

注意,在 plugin 裏使用Vue.xxx 註冊的方法,無法在建立 Vue 實體,即是 new Vue() 之後使用。因此無法在元件使用。否則會報錯:

created() {
    this.greeting()
}
// this.greeting is not a function

以上明顯看到,plugin 比第一個提到的方法更強大,可以同時在裏面使用 Vue.prototype、mixin、directive。就如字面 plugin 的意思一樣,此做法通常是用來開發插件時會用到。

mixin

相信大家對 mixin 都不陌生了,在此就不累贅重複。簡單講,mixin 的作用就把重用的功能抽出來,讓我們在不同元件裏,可以重用此功能。

局部使用

最常見做法是,把會重用的功能拆出來,獨立成為一個 JS 檔案,然後把裏面的程式碼 export 出去。最後在元件裏把它 import 進來。

一個常用到的例子,就是當打 API 出錯時,就會彈跳出 error。

mixins/ showAlert.js

export default {
    methods: {
        showError() {
            alert('載入資料失敗')
        }
    },
}

Home.vue 裏使用 showAlert 這個 mixin

import showAlert from '@/mixins/showAlert.js'

export default {
  // 引入 mixin
  mixins: [showAlert],
  created() {
    this.showError()
  }
}

以上看到,在 Home.vue 裏,即使沒有建立 showError 這個 methods,但我們透過 mixins,把 showError 這個函式,合併到 Home.vue 的 methods 裏,因此 Home.vue 現在其實是這樣:

export default {
  methods: {
      showError() {
        alert('載入資料失敗')
      }
  }
  created() {
    this.showError()
  }
}

除了 methods,也可以用 data、created、mounted 等,如同寫 Vue 元件時一樣。

全域使用

跟 Vue.prototype 的效果一樣,如果全域註冊 mixin的話,每個元件都會用到此 mixin,不用每次都要自行 import 進來才用到。

main.js

Vue.mixin({
  methods: {
      showError() {
        alert('載入資料失敗')
      }
  }
})

Vue 3 的做法

升級到 Vue 3 後,現在是使用 createApp() 來建立 Vue 應用程式,因此不再把全局方法註冊在 Vue 身上,而是在 app 上建立。

import { createApp } from 'vue'
const app = createApp({})

截圖自官方文件

以前 Vue 2 是直接在 Vue 的原型上使用這些全域 API,即是 Vue.use()Vue.mixin()。因此會出現 Vue 實體被污染的情況,因為所有 Vue 實體都是由 new Vue() 此建構函式來建立,因此每一個實體也有共享了這些全域方法。

官方說明:

// 这会影响到所有根实例
Vue.mixin({
  /* ... */
})

const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })

為了避免此問題,Vue 使用了 createApp 的手法來建立 Vue 的實體,而所有全域註冊的 mixin、plugin 等,全都會在該 app 上,而不是 Vue 原型上。

所以,當你建立一個 Vue CLI 專案時,你會發現 router 是註冊在某個 app 上。

const { createApp } = Vue

const app = createApp({})
app.use(router).mount('#app')

除了以上的變更,Vue 3 新增了 provide / inject 語法,官方更表示可以代替使用 app.config.globalProperties

provide / inject

全域註冊 provide

通常我們會在跨元件傳資料時,才會用到 provideinject,但其實同樣可以用來實現全域註冊:

main.js

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.provide('message', function() {
    console.log('來自全域的 provide')
})

app.use(router).mount('#app')

Home.vue

export default {
  inject: {
    msg: {
      from: 'message'
    }
  },
  created() {
    this.msg() // 來自全域的 provide
  }
}

局部註冊 provide

這應該是相對常用的方法,特別是跨元件傳資料時就非常方便。不用一直不斷用 props 往下傳資料。

舉例說: Home --> ProductInfo --> ProductComments
要把在 Home 的資料傳到 ProductComments。

Home.vue

export default {
  ...,
  data() {
    return {
      comments: ['Nice product', 'Cool!']
    }
  },
  provide() {
    return {
      comments: this.comments
    }
  }
}

ProductComments.vue

export default {
    inject: ['comments'],
    created() {
        console.log(this.comments) // ['Nice product', 'Cool!']
    }
}

總結

  • Vue 2 可使用 Vue.prototype、plugin 和 mixin 全域註冊方法。
  • Vue 3 同樣可以使以上這些方法,但語法稍為不同。
  • Vue 3 新增 provide/inject 語法,也可以用來全域註冊方法。

參考資料

Vue3 的資料狀態管理,provide / inject、vuex、props
Global API
Vue中如何使用插件?(Plugins)【Vue面试题】


上一篇
不只懂 Vue 語法:為何懶加載路由和元件會提升網頁效能?
下一篇
不只懂 Vue 語法:試說明 Composition API 與 Options API 概念和語法的分別?
系列文
不只懂 Vue 語法:Vue.js 觀念篇31

尚未有邦友留言

立即登入留言