在Vue中提供了use方法来安装插件,那么Vue插件的原理是什么呢?

一、Vue.use

use方法官方描述如下图:

image.png

也就是说Vue.use()方法接收一个函数或者提供install方法的对象作为参数(必须提供install方法),如果传入的参数是函数,这个函数会被当做install方法。

Vue2.6.11版本use源码如下:

1628576718.png

这段源码很好理解,initUse函数中给Vue添加了一个静态方法use,use方法接收一个参数plugin,判断参数是对象还是函数并执行相关逻辑。

以vuex3.6.2版本为例,直接看源码:

1628578420.png

它暴露了一个install方法,验证了我们前面的学习:Vue.use(vuex)是使用这个install方法来安装插件的。

那么vuex的install函数做了什么?

1628578484.png

可以看到它调用了applyMixin函数并把Vue传过去了

applyMixin函数源码如下:

1628578515.png

如果是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函数进行选项合并

image.png

其它的插件开发原理是差不多的,官方文档如下:

1628578597.png

三、自己实现一个插件

现在,我们来实现一个提示框插件,要求可以通过this.$notify()来进行调用,并且可以传入自定义模板

分析需求:

  • 要实现一个Vue插件,首先按照Vue.use的约定我们需要提供一个对象,这个对象必须要有install函数,或者提供一个函数
  • 通过this来调用它,说明这个方法需要挂在Vue的原型对象上,所以我们需要给Vue的prototype添加一个$notify方法
  • 可以传入自定义模板,说明我们需要调用API来编译传入的模板

现在我们创建一个vue工程,在src目录下新建plugin目录,然后创建一个notify目录,新建index.js和Notify.vue

1628578892.png

你可能会好奇为什么要新建一个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()来进行提示

1628579326.png

接着我们需要实现传入模板并且显示出来,那么问题来了,传入模板好理解,怎么编译模板并且显示出来呢?

这就要用到Vue给我们提供的$mount API了,官方描述如下:

1628579357.png

既然可以手动挂载一个实例,那么我们可以创建一个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选项,这在官方文档上说的很清楚

1628579502.png

代码如下:

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中了,不出意外你将在页面右下角找到下图显示的框

1628579602.png

接下来要实现传入模板,vue中也为我们提供了v-html指令来插入模板

image.png

我们需要做的只是在调用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中传递一段模板

1628579817.png

页面上操作的效果为下图,且两秒后消失

1628579843.png

作者:|小陈同志丶Go|,原文链接: http://www.imooc.com/article/331144

文章推荐

表达式求值

通过redis学网络(2)-redis网络模型

JUC并发编程原理精讲(源码分析)

介绍一个.Net远程日志组件

【性能优化】优雅地优化慢查询:缓存+SQL修改组合拳

设计模式(二十六)----行为型模式之备忘录模式

交换机的简单命令小结

带你深入Java Log框架,彻底搞懂Log4J、Log4J2、LogBack,SL...

JAVA重试机制多种方式深入浅出

Spring Ioc源码分析系列--Bean实例化过程(二)

评估指标与评分(上):二分类指标

.NET性能优化-推荐使用Collections.Pooled