在Vue3 <script setup> 中如何调用自定义全局方法

🐶 问题背景

在最近的项目开发中,有一个水印的需求。这个水印功能,在一个已知的 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 (依赖注入)

🐮Vue 依赖/注入

同样是插件,在遇到这一问题时,我首先想到我们最常用的依赖如 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中推荐使用依赖注入的方式来访问我们的全局属性和方法。

🐻附录(示例源码)

源码

相关推荐
呆呆小雅22 分钟前
四、Vue 条件语句
前端·javascript·vue.js
山沟沟里的娃40 分钟前
pinia从0到1
vue.js
Tirzano1 小时前
vue3 ts 动态表单原理
前端·javascript·vue.js
m0_748240912 小时前
Vue.js前端框架教程12:Vue表单验证rules和form.validate
javascript·vue.js·前端框架
武昌库里写JAVA3 小时前
Golang内存管理与优化
数据结构·vue.js·spring boot·算法·课程设计
reembarkation3 小时前
vue2中使用 v-html 指令渲染的标签, 标签内绑定的 click 事件
前端·vue.js·html
shadowflies4 小时前
组件库TDesign的表格<t-table>的使用,行列合并以及嵌入插槽实现图标展示,附踩坑
前端·javascript·vue.js·vue·tdesign
LCG元4 小时前
Vue.js组件开发-图片加载失败自动显示默认图片
vue.js
blzlh6 小时前
Vue 数据驱动页面,让我们专注于业务开发
前端·vue.js·程序员
萧大侠jdeps6 小时前
Vue 3 与 Tauri 集成开发跨端APP
前端·javascript·vue.js·tauri