🐶 问题背景
在最近的项目开发中,有一个水印的需求。这个水印功能,在一个已知的 vue2 项目中已经实现,在vue2中,将这个水印封装成了一个 watermark plugin,方法是直接在 vue 实例原型上添加 <math xmlns="http://www.w3.org/1998/Math/MathML"> w a t e r m a r k 对象,这样在项目中,可直接使用 ' t h i s . watermark 对象,这样在项目中,可直接使用 ` this. </math>watermark对象,这样在项目中,可直接使用'this.watermark ` 来调用 watermark 插件中定义的方法。
javascript
// Vue2中书写方法
export default {
set: () => {
console.log('set watermark')
},
show: () => {
console.log('show watermark')
},
hide: () => {
console.log('hide watermark')
},
clear: () => {
console.log('clear watermark')
},
install (app, options) {
const watermark = this
app.prototype.$watermark = watermark
}
}
在vue3中已不支持以 Vue.prototype 的形式直接在 Vue 对象原型上添加自定义属性了,但是提供了另一种注册全局属性的方法:app.config.globalProperties
一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。
这是对 Vue 2 中
Vue.prototype
使用方式的一种替代,此写法在 Vue 3 已经不存在了。与任何全局的东西一样,应该谨慎使用。
在 Vue3 中新的插件修改为如下形式:
javascript
const watermark = {
set: () => {
console.log('set watermark')
},
show: () => {
console.log('show watermark')
},
hide: () => {
console.log('hide watermark')
},
clear: () => {
console.log('clear watermark')
},
install: function (app, options) {
const watermark = this
// Here is the plugin register operation
app.config.globalProperties.$watermark = watermark
}
}
export default watermark
然后在 vue项目的入口文件(main.js)中安装插件即可:
javascript
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import watermark from './plugin/watermark.js'
const app = createApp(App)
app
.use(watermark)
.mount('#app')
接下来使用一下,在 App.vue 中编写代码,测试一下该方法:
xml
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
created () {
this.$watermark.set() // we call the watermark.set() function
}
}
</script>
结果为:
可以看到,这里这个方法成功调用了。
我们使用的是选项式写法,可以直接通过访问this的形式调用。
但是,我的vue3新项目使用的是
🐹 解决方法
以下这个问题的2种解决方案:
1. getCurrentInstance
getCurrentInstance 方法是 vue3 提供的一个 api, 该api可以访问组件内部实例。
(不过现在查看官方文档时,已查询不到关于该api的信息,早在以前的官方说明中就已经指出,该api仅暴露给高阶使用场景,反对在业务代码中把此方案当做获取this的替代方案)。
xml
<!--App.vue-->
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { getCurrentInstance } from 'vue'
const internalInstance = getCurrentInstance()
internalInstance.appContext.config.globalProperties.$watermark.set()
</script>
以下是结果:
可以看到,该方法能正常调用我们的自定义全局方法。
不过,既然该方案官方已明确不推荐在应用系统中调用,那我们也应该避免使用。
2. Provide/Inject (依赖注入)
同样是插件,在遇到这一问题时,我首先想到我们最常用的依赖如 VueRouter 是怎么做的呢?在 vue-router 4.x 中,vue-router 暴露了2个 composition api 来获取 route 和 router。
javascript
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
同样,我们的插件也可以如此实现。于是,查看了一下 vue-router 的源码文件,发现 vue-router 是基于 vue 中的依赖注入实现的。
javascript
// vue-router.mjs
const createRouter = function () {
...
const router = {
...
install (app) {
const router = this
...
app.provide(routerKey, router)
}
}
return router
}
const useRouter = function () {
return inject(routerKey)
}
同样的,我们使用与 vue-router 相同的方法来实现:
javascript
// src/plugin/watermark.js
import { inject } from 'vue'
const watermark = {
set: () => {
console.log('set watermark')
},
show: () => {
console.log('show watermark')
},
hide: () => {
console.log('hide watermark')
},
clear: () => {
console.log('clear watermark')
},
install (app, options) {
const watermark = this
app.config.globalProperties.$watermark = watermark
app.provide('watermark', watermark)
}
}
const useWaterMark = function () {
return inject('watermark')
}
export {
watermark,
useWaterMark
}
xml
<!--App.vue-->
<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<HelloWorld msg="Vite + Vue" />
</template>
<script setup>
import HelloWorld from './components/HelloWorld.vue'
import { getCurrentInstance } from 'vue'
import { useWaterMark } from './plugin/watermark.js'
const watermark = useWaterMark()
watermark.hide()
</script>
以下是实现结果:
在应用中使用时,在vue3中推荐使用依赖注入的方式来访问我们的全局属性和方法。