# Vue3 自定义插件
插件是自包含的代码,通常向 Vue 添加全局级功能。它可以是公开 install()
方法的 object
,也可以是 function
。
# 基本逻辑
// plugins/myPlugins.js
export default {
install: (app, options) => {
// Plugin code goes here
}
}
1
2
3
4
5
6
2
3
4
5
6
# 常见定义
// plugins/myPlugins.js
export default {
// 一个简单的i18n翻译转化
install: (app, options) => {//接收一个 key 字符串,并使用在用户提供的选项中查找转换后的字符串
app.config.globalProperties.$translate = (key) => {
return key.split('.')
.reduce((o, i) => { if (o) return o[i] }, options)
}
app.provide('i18n', options)
app.directive('my-directive', {
mounted (el, binding, vnode, oldVnode) {
// some logic ...
}
...
})
app.mixin({
created() {
// some logic ...
}
...
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 使用
1.全局注册
//main.ts
import { createApp } from 'vue'
import App from './App.vue'
import myPlugins from './plugins/myPlugins.js'
const app = createApp(App)
const myPluginStrings = {
greetings: {
hi: 'Hallo!'
}
}
//用于查找的翻译字典对象则应当在插件被安装时作为 app.use() 的额外参数被传入
app.use(myPlugins, myPluginStrings)
app.mount('#app')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
$translate('greetings.hi') // 'Hallo!'
1
- Provide / Inject
将插件接收到的
options
参数提供给整个应用,让任何组件都能使用这个翻译字典对象。
<script setup>
import { inject } from 'vue'
const i18n = inject('i18n')
console.log(i18n.greetings.hello)
</script>
1
2
3
4
5
6
7
2
3
4
5
6
7
# 复杂实例:挂载 VNode ,并 添加全局方法
# VNode
// component/Loading.vue
<template>
<div v-if="flag">true</div>
<div v-else="flag">false</div>
</template>
<script setup lang="ts">
import { ref, defineExpose } from 'vue'
let flag = ref<Boolean>(false)
const flagTrueFn = () => {
flag.value = true
}
const flagFalseFn = () => {
flag.value = false
}
defineExpose({
flag,
flagTrueFn,
flagFalseFn
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 编写插件
// component/index.ts
import { App, createApp, h, render, VNode, createVNode } from "vue";
import loading from "./loading.vue";
export default {
install(app: App, options: any) {
//方法一:使用 render 函数实例化插件并挂载到 body 中
// const vnode: VNode = createVNode(loading);
// render(vnode, document.body);
// app.config.globalProperties.$loading = {
// flag: vnode.component?.exposed?.flag,
// flagTrueFn: vnode.component?.exposed?.flagTrueFn,
// flagFalseFn: vnode.component?.exposed?.flagFalseFn,
// };
//方法二:使用 createApp.mount 挂载实例
const plugin = createApp(loading);
const vnode = plugin.mount(document.createElement("div"));
document.body.appendChild(vnode.$el);
app.config.globalProperties.$loading = {
flag: vnode.flag,
flagTrueFn: vnode.flagTrueFn,
flagFalseFn: vnode.flagFalseFn,
};
},
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 挂载插件
import { createApp } from "vue";
import App from "./App.vue";
import Loading from "./components/loading";
let app = createApp(App);
//没有类型限制,可能会 ts 警告,需要给它定义泛型
declare module "@vue/runtime-core" {
export interface ComponentCustomProperties {
flag:Boolean
flagTrueFn: () => void;
flagFalseFn: () => void;
}
}
app.use(Loading);
app.mount("#app");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 组件中使用
<script setup lang="ts">
import { ComponentInternalInstance, getCurrentInstance } from 'vue'
// getCurrentInstance 支持访问内部组件实例,类似于 this。
const { appContext } = getCurrentInstance() as ComponentInternalInstance
const changeFlag = () => {
appContext.config.globalProperties.$loading.flagTrueFn()
console.log(appContext)
setTimeout(() => {
appContext.config.globalProperties.$loading.flagFalseFn()
}, 2000)
}
</script>
<template>
<Button @click="changeFlag">切换</Button>
</template>
<style>
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 拓展:Vue2 方法
# 对外暴露一个install方法
暴露了一个 install
方法。其第一个参数是vue,第二个参数是自定义传参(可选的选项对象)
let MyPlugin={}
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) {
// 逻辑...
}
}
export default MyPlugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 全局引入后注册(也可以传入一个可选的选项对象)
Vue.use(MyPlugin, { options: '一个插件' })
1
# 相关
使用方法四直接挂载到原型上,虽然所有的实例都能访问到,但有一个风险:即同时也可以通过访问来修改该方法,存在风险,不严谨。
可以使用js原生方法Object.defineProperty()方法代替
# 封装echarts插件案例
import echarts from "echarts"
let MyPlugin = {}
MyPlugin.install = function (Vue) {
Object.defineProperties(Vue.prototype, {
//$myChart即为实例上添加的一个方法名
$myChart: {
//此处只用到get()没有用set()因为不涉及修改和双向绑定
get() {
return {
//绘制折线图
line(id) {
//以下来自echarts折线图官方配置
// 基于准备好的dom,初始化echarts实例
var myChart = echarts.init(document.getElementById(id))
// 指定图表的配置项和数据
var option = {
title: {
text: "ECharts 入门示例",
},
tooltip: {},
legend: {
data: ["销量"],
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
},
yAxis: {},
series: [
{
name: "销量",
type: "bar",
data: [5, 20, 36, 10, 10, 20],
},
],
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
},
//绘制地图
chinaMap(){
...
}
}
}
}
})
}
export default MyPlugin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
在main.js中use后,在任意组件中使用以下代码即表示使用折线图(折线图的相关样式规范参考echarts官网)
this.$myChart.line('main')
1