在Vue中提供了use方法来安装插件,那么Vue插件的原理是什么呢?
一、Vue.use
use方法官方描述如下图:
也就是说Vue.use()方法接收一个函数或者提供install方法的对象作为参数(必须提供install方法),如果传入的参数是函数,这个函数会被当做install方法。
Vue2.6.11版本use源码如下:
这段源码很好理解,initUse函数中给Vue添加了一个静态方法use,use方法接收一个参数plugin,判断参数是对象还是函数并执行相关逻辑。
以vuex3.6.2版本为例,直接看源码:
它暴露了一个install方法,验证了我们前面的学习:Vue.use(vuex)是使用这个install方法来安装插件的。
那么vuex的install函数做了什么?
可以看到它调用了applyMixin函数并把Vue传过去了
applyMixin函数源码如下:
如果是vue2.x版本,vuex直接使用Vue.mixin来扩展功能,它利用混入的钩子会在组件钩子之前调用的特性,给应用添加一个beforeCreate钩子,在这里初始化vuex插件。
二、mixin
不得不说一下mixin
what:mixin是一种js的设计模式,可以轻松被子类继承功能,目的是函数复用,Vue中也应用了这一设计模式,通过Vue.mixin可以用来分发可复用逻辑。
why:试想,当多个组件具有类似的数据和方法时,是不是可以将这些数据和方法提取出来,通过混入的方式将逻辑注入到需要这些数据和方法的组件中呢?这将会为我们节省很多代码,也是Vue设计mixin的理由
how:使用方式有两种:全局混入和局部混入,具体使用方式可参考文档:https://cn.vuejs.org/v2/guide/mixins.html#基础
注意:尽量避免使用全局混入,因为它会影响所有的vue实例(有时候莫名其妙功能被搞乱了都不知道咋回事,排查非常困难),如果组件中与mixin中具有同名的属性,会进行选项合并,除了生命周期外,其它的所有属性都会被组件自身的属性覆盖,与继承的原理是类似的,如果子类本身有这个属性或方法就不会再沿着原型链往上找了,生命周期会被合并成数组,混入的钩子会在组件的钩子之前被调用。
mixin的源码非常简单,只是调用mergeOptions函数进行选项合并
其它的插件开发原理是差不多的,官方文档如下:
三、自己实现一个插件
现在,我们来实现一个提示框插件,要求可以通过this.$notify()来进行调用,并且可以传入自定义模板
分析需求:
- 要实现一个Vue插件,首先按照Vue.use的约定我们需要提供一个对象,这个对象必须要有install函数,或者提供一个函数
- 通过this来调用它,说明这个方法需要挂在Vue的原型对象上,所以我们需要给Vue的prototype添加一个$notify方法
- 可以传入自定义模板,说明我们需要调用API来编译传入的模板
现在我们创建一个vue工程,在src目录下新建plugin目录,然后创建一个notify目录,新建index.js和Notify.vue
你可能会好奇为什么要新建一个Notify.vue,不急,后面会讲
按照我们前面的分析,我们知道一个插件的基本结构大致如下:
// index.js
function notify() {
function install(Vue) {
}
return { install };
}
export default notify();
然后我们要给Vue的原型对象添加KaTeX parse error: Expected 'EOF', got '方' at position 7: notify方̲法,这样我们可以通过this.notify()来调用
// index.js
function install(Vue) {
Vue.prototype.$notify = notifyInit;
}
function notifyInit(options) {
console.log('调用成功')
}
这样一个插件的基本功能就实现了,我们可以通过Vue.use来使用它,在main.js中:
// main.js
import notify from './plugins/notify/index'
Vue.use(notify);
然后我们到App.vue中验证一下功能是否正常,点击的时候调用this.notify()
<template>
<div id="app">
<button @click="handleShow">显示</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
handleShow() {
this.$notify()
}
}
}
</script>
到这一步功能正常,我们已经实现了调用this.notify()来进行提示
接着我们需要实现传入模板并且显示出来,那么问题来了,传入模板好理解,怎么编译模板并且显示出来呢?
这就要用到Vue给我们提供的$mount API了,官方描述如下:
既然可以手动挂载一个实例,那么我们可以创建一个Vue组件,在组件中将dom、js、style都创建好,最后在调用$notify的时候将挂载的元素插入到文档中,这就是Notify.vue的作用了
Notify.vue我们先简单创建好结构,代码如下:
<template>
<div class="notify" v-if="isShow">
<div id="content"></div>
</div>
</template>
<script>
export default {
name: "notify",
};
</script>
<style>
.notify #content {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 999;
width: 200px;
height: 150px;
border-radius: 5px;
border: 1px solid #e5e5e5;
}
</style>
在index.js中,我们需要引入这个组件,通过install方法中注入的Vue来完成功能,分为如下几步:
- 调用Vue.extend声明扩展单文件组件Notify
- 获取Notify组件的实例
- 调用$mount来挂载实例
- 获取KaTeX parse error: Expected 'EOF', got '并' at position 3: el并̲将el插入到文档中
注意:实例挂载之后才可以访问$el选项,这在官方文档上说的很清楚
代码如下:
import Notify from "./Notify.vue";
function notify() {
function install(Vue) {
elementInit(Vue);
Vue.prototype.$notify = notifyInit;
}
function elementInit(Vue) {
// 声明扩展组件
const ClassNotify = Vue.extend(Notify);
// 获取组件实例
const instance = new ClassNotify();
// 挂载组件,添加el选项
instance.$mount();
// 获取el
const el = instance.$el;
// 插入el
document.body.appendChild(el);
}
function notifyInit(options) {
console.log('调用成功')
}
return {
install,
};
}
export default notify();
现在我们已经将组件成功插入到body中了,不出意外你将在页面右下角找到下图显示的框
接下来要实现传入模板,vue中也为我们提供了v-html指令来插入模板
我们需要做的只是在调用this.notify()方法的时候,将接收到的配置参数传递给Notify组件,由Notify组件通过v-html指令来进行渲染
如果我们直接在notifyInit函数中访问Notify实例,显然是访问不到的,this指向的是当前调用$notify方法的组件,本例中也就是App.vue
那么借助$mount调用返回vm(实例自身)的特性,我们可以将Notify组件存下来,然后再到notifyInit函数中进行访问
import Notify from "./Notify.vue";
function notify() {
let NotifyComponent;
function install(Vue) {
elementInit(Vue);
Vue.prototype.$notify = notifyInit;
}
function elementInit(Vue) {
// 声明扩展组件
const ClassNotify = Vue.extend(Notify);
// 获取组件实例
const instance = new ClassNotify();
// 挂载组件,添加el选项,并将vm赋值给NotifyComponent
NotifyComponent = instance.$mount();
// 获取el
const el = instance.$el;
// 插入el
document.body.appendChild(el);
}
function notifyInit(options) {
// 调用notify组件的方法,将配置参数透传过去
NotifyComponent.notify(options);
}
return {
install,
};
}
export default notify();
然后我们只需要在Notify.vue中新增该方法来接收参数即可
<template>
<div class="notify" v-if="isShow">
<div id="content" v-html="content"></div>
</div>
</template>
<script>
export default {
name: "notify",
data() {
return {
isShow: false,
content: "张三",
};
},
methods: {
notify(options) {
this.isShow = true;
// 两秒后隐藏
setTimeout(() => {
this.isShow = false;
}, 2000)
// 传了配置参数则使用,否则使用默认
options && options.content && (this.content = options.content);
},
},
};
</script>
<style>
.notify #content {
position: fixed;
bottom: 10px;
right: 10px;
z-index: 999;
width: 200px;
height: 150px;
border-radius: 5px;
border: 1px solid #e5e5e5;
}
</style>
此时我们在App.vue中传递一段模板
页面上操作的效果为下图,且两秒后消失