不只懂 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面试题】


<<:  第 22 集:Bootstrap 客制化 utilities(下)

>>:  追求JS小姊姊系列 Day22 -- 工具人、姐妹不只身份的差别(中):从识别字开始讲起吧

Day3 Virtual DOM vs Actual DOM

在介绍react实例之前,首先来介绍一下其操作原理---Virtual DOM,并比较和Actual...

是 POC 还是 RC? | 用 C# 实作 Windows 剪贴簿管理工具

前言 在 macOS 我使用 Clipy 来管理我的剪贴簿,它不但可以储存你所复制过的东西还可以自订...

C# delegate 委派 实战篇

我最早开始使用委派 是在开发游戏功能的时候 当时有个需求是需要写一个角色升级的功能 (当年是个人吃人...

Day11 休息一日放假一下

抱歉惹,最近实在太累,上一个系列对我来说资讯量有点庞大,看了很久才搞懂内容是什麽,每天上班玩回来看个...

Day 10 ( 中级 ) 超时空跑马灯 ( 广播 )

超时空跑马灯 ( 广播 ) 教学原文参考:超时空跑马灯 ( 广播 ) 这篇文章会介绍如何使用「发送数...