开发vue插件
- 前言
- 一、什么是vue插件?
- 二、vue插件的使用
- 三、vue插件的开发
- 1. 编写 dialog 的本体
- 2. 手动挂载 dialog 实例的 dom
- 3. 暴露 install 方法给 Vue.use() 使用
- 4. 引用插件
- 5. 使用插件
前言
vue 项目开发过程中,经常用到插件,比如原生插件 vue-router、vuex,还有 element-ui 提供的 notify、message 等等。这些插件让我们的开发变得更简单高效。那么 vue 插件是怎么开发的呢?
需要涉及的技术点:
- vue 插件的本质;
- Vue.extend() 全局方法;
- 如何手动挂载 Vue 实例;
- Vue.use() 的原理;
- 如何打包成 umd 格式;
一、什么是vue插件?
什么是 Vue插件 ?它和 Vue组件 有什么区别?我们来看一下官网的解释:
- “插件” 通常用来为 Vue 添加全局功能。
- “组件” 是可复用的 Vue 实例,且带有一个名字。
其实, Vue 插件 和 Vue组件 只是在 Vue.js 中包装的两个概念而已,不管是插件还是组件,最终目的都是为了实现 逻辑复用 。它们的本质都是对代码逻辑的封装,只是封装方式不同而已。在必要时,组件也可以封装成插件,插件也可以改写成组件,就看实际哪种封装更方便使用了。
除此之外,插件是全局的,组件既可以全局注册也可以局部注册。
我们今天只聚焦 Vue 插件。
插件一般有下面几种:
- 添加全局方法或者属性,如: vue-custom-element;
- 添加全局资源:指令/过滤器/过渡等,如 vue-touch;
- 通过全局混入来添加一些组件选项,如 vue-router;
- 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现;
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router;
二、vue插件的使用
插件需要通过 Vue.use() 方法注册到全局,并且需要在调用 new Vue() 启动应用之前完成。之后在其他 vue 实例里面就可以通过 this.$xxx 来调用插件中提供的 API 了。
下面以实现一个简易的 弹框插件 dialog 为例,给大家讲解下怎么一步一步地开发并发布一个 Vue 插件。
希望达到的效果:
在 main.js 中 引入:
// src/main.js
import Vue from 'vue'
import dialog from '@bluehe/dialog'
Vue.use(dialog)
在 App.vue或其他组件 的 方法里调用 this.$dialog():
// src/App.vue
<template>
<div>
<button @click='handleClick'>click</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
handleClick() {
this.$dialog({
header: '我的弹窗',
content: 'This is a dialog'
})
}
}
}
</script>
运行后在页面上点击按钮,弹出弹框,当点击关闭按钮时,弹框消失:
三、vue插件的开发
1. 编写 dialog 的本体
在 src 目录下创建 components/Dialog/index.vue 文件:
// src/components/Dialog/index.vue
<template>
<transition name="fade">
<div id="dialog" class="back" v-if="isShow">
<div id="div1" class="content">
<div id="close">
<span id="close-button" @click="close">×</span>
<!-- 弹窗标题 -->
<h2>{{title}}</h2>
</div>
<div id="div2">
<!-- 弹窗内容 -->
<p>{{content}}</p>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'index',
data () {
return {
isShow: false,
title: '标题',
content: ''
}
},
mounted () {
this.isShow = true
},
methods: {
close () {
this.isShow = false
}
}
}
</script>
<style scoped>
#open_btn {
background: #ddd;
}
#dialog {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
}
#div1 {
background:#eeeeee;
width: 700px;
z-index: 1;
margin: 12% auto;
overflow: auto;
}
span {
padding-top: 12px;
cursor: pointer;
padding-right: 15px;
}
#div2 {
background:#eeeeee;
margin: auto;
height: 300px;
padding: 0 20px;
}
#close {
padding: 5px;
background: #ddd;
}
#close-button {
float: right;
font-size: 30px;
}
h2 {
margin: 10px 0;
padding-left: 15px;
}
</style>
现在 dialog 本体完成了,但是它里面的数据目前没法改变,因为我没有给它定义 props 属性。这不是 bug,而是,插件并不是通过 props 来传值的。
2. 手动挂载 dialog 实例的 dom
为了给插件传值,可以利用基础 vue 构造器 Vue.extend() 创建一个“子类”。这个子类相当于一个继承了 Vue 的 Dialog 构造器。然后在 new 这个构造函数的时候,给 Dialog 的 data 属性传值,然后手动调用这个实例的 $mount() 方法手动挂载,最后使用原生 js 的 appendChild 将真实 DOM (通过实例上的 $el 属性获取)添加到 body 上。
在 src 目录下新建 components/Dialog/index.js 文件:
// src/components/Dialog/index.js
import Vue from 'vue';
import Dialog from './index.vue';
// 使用 Vue.extend() 创建 Dialog 的构造器
const DialogConstructor = Vue.extend(Dialog);
const dialog = function(options = {}) {
// 创建 dialog 实例,通过构造函数传参,
// 并调用 Vue 实例上的 $mount() 手动挂载
const dialogInstance = new DialogConstructor({
data: options
}).$mount();
// 手动把真实 dom 挂到 html 的 body 上
document.body.appendChild(dialogInstance.$el);
return dialogInstance;
};
// 导出包装好的 dialog 方法
export default dialog;
3. 暴露 install 方法给 Vue.use() 使用
为了支持 Vue.use(),vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象。
通过 vue.js 源码也可以看出,Vue.use() 方法所做的事情就是调用插件或者组件的 install 方法,然后把全局 Vue 传进去供插件和组件使用。
vue 源码:
// https://github.com/vuejs/vue/blob/dev/src/core/global-api/use.js
/* @flow */
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
在 src 目录下新建 components/index.js 文件,定义一个 install 方法,在里面将 dialog 实例 放到 Vue.prototype 上作为 Vue 实例的方法暴露到全局:
// src/components/index.js
import dialog from './Dialog/index';
// 准备好 install 方法 给 Vue.use() 使用
const install = function (Vue) {
if (install.installed) return;
install.installed = true;
// 将包装好的 dialog 挂到Vue的原型上,作为 Vue 实例上的方法
Vue.prototype.$dialog = dialog;
}
// 默认导出 install
export default {
install
};
现在插件就开发完成了,我们可以在当前项目中本地引用这个插件了。
4. 引用插件
在 main.js 中引入插件:
// main.js
import dialog from src/components/index.js;
Vue.use(dialog);
5. 使用插件
在App.vue中 使用插件:
// App.vue
<template>
<div>
<button @click="handleClick">click</button>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
handleClick () {
this.$dialog({
title: '我的弹窗',
content: 'This is a dialog'
})
}
}
}
</script>