一直都以为vue的插件生涩难懂,可是又很好奇,在看了几篇文章,试着写了写以后以为也没那么难,这篇文就是总结一下这个过程,加深记忆,也能够帮助后来的人。css
在学习以前,先问问本身,为何要编写vue的插件。html
在一个项目中,尤为是大型项目,有不少部分须要复用,好比加载的loading动画,弹出框。若是一个一个的引用也稍显麻烦,并且在一个vue文件中引用的组件多了,会显得代码臃肿,因此才有了封装vue插件的需求。vue
说完需求,就来看看具体实现。目前我尝试了两种不同的插件编写的方法,逐个介绍。web
这是个人项目目录,大体的结构解释这样,尽可能简单,容易理解。app
一个是loading插件,一个是toast插件,不一样的地方在于:loading插件是做为组件引入使用,而toast插件是直接添加在挂载点里,经过方法改变状态调用的。ssh
目前使用起来是酱紫的:函数
toast文件下有两个文件,后缀为vue的文件就是这个插件的骨架,js文件一个是将这个骨架放入Vue全局中,并写明操做逻辑。工具
能够看一下toast.vue的内容:学习
<template> <transition name="fade"> <div class="toast" v-show="show"> {{message}} </div> </transition> </template> <script> export default { data() { return { show: false, message: "" }; } }; </script> <style lang="scss" scoped> .toast { position: fixed; top: 40%; left: 50%; margin-left: -15vw; padding: 2vw; width: 30vw; font-size: 4vw; color: #fff; text-align: center; background-color: rgba(0, 0, 0, 0.8); border-radius: 5vw; z-index: 999; } .fade-enter-active, .fade-leave-active { transition: 0.3s ease-out; } .fade-enter { opacity: 0; transform: scale(1.2); } .fade-leave-to { opacity: 0; transform: scale(0.8); } </style>
这里面主要的内容只有两个,决定是否显示的show
和显示什么内容的message
。动画
粗看这里,有没有发现什么问题?
这个文件中并无props
属性,也就是不管是show也好,message也好,就没有办法经过父子组件通讯的方式进行修改,那他们是怎么正确处理的呢。别急,来看他的配置文件。
index.js:
import ToastComponent from './toast.vue' const Toast = {}; // 注册Toast Toast.install = function (Vue) { // 生成一个Vue的子类 // 同时这个子类也就是组件 const ToastConstructor = Vue.extend(ToastComponent) // 生成一个该子类的实例 const instance = new ToastConstructor(); // 将这个实例挂载在我建立的div上 // 并将此div加入全局挂载点内部 instance.$mount(document.createElement('div')) document.body.appendChild(instance.$el) // 经过Vue的原型注册一个方法 // 让全部实例共享这个方法 Vue.prototype.$toast = (msg, duration = 2000) => { instance.message = msg; instance.show = true; setTimeout(() => { instance.show = false; }, duration); } } export default Toast
这里的逻辑大体能够分红这么几步:
这里须要着重提的是Vue.extend()
。举个例子,咱们平常使用vue编写组件是这个样子的:
Vue.component('MyComponent',{ template:'<div>这是组件</div>' })
这是全局组件的注册方法,但其实这是一个语法糖,真正的运行过程是这样的:
let component = Vue.extend({ template:'<div>这是组件</div>' }) Vue.component('MyComponent',component)
Vue.extend会返回一个对象,按照大多数资料上说起的,也能够说是返回一个Vue的子类,既然是子类,就没有办法直接经过他使用Vue原型上的方法,因此须要new一个实例出来使用。
在代码里console.log(instance)
得出的是这样的结果:
能够看到
$el:div.toast
也就是toast组件模板的根节点。
疑惑的是,我不知道为何要建立一个空的div节点,并把这个实例挂载在上面。我尝试注释这段代码,可是运行会报错。
查找这个错误的缘由,貌似是由于
document.body.appendChild(instance.$el)
这里面的instance.$el
的问题,那好,咱们console下这个看看。WTF!!!!结果竟然是undefined
。
那接着
console.log(instance)
和上一张图片比对一下,发现了什么?对,$el消失了,换句话说在我注释了
instance.$mount(document.createElement('div'))
这句话以后,挂载点也不存在了。接着我试着改了一下这句:
instance.$mount(instance.$el)
$el又神奇的回来了………………
暂时没有发现这种改动有什么问题,能够和上面同样运行。但不管如何,这也就是说instance实例必须挂载在一个节点上才能进行后续操做。
以后的代码就简单了,无非是在Vue的原型上添加一个改变插件状态的方法。以后导出这个对象。
接下来就是怎么使用的问题了。来看看main.js是怎么写的:
import Vue from 'vue' import App from './App' // import router from './router' import Toast from './components/taost' Vue.use(Toast) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ // router, render: h => h(App) }).$mount('#app')
这样就能够在其余vue文件中直接使用了,像这样:
// app.vue <template> <div id="app"> <loading duration='2s' :isshow='show'></loading> <!-- <button @click="show = !show">显示/隐藏loading</button> --> <button @click="toast">显示taost弹出框</button> </div> </template> <script> export default { name: "app", data() { return { show: false }; }, methods: { toast() { this.$toast("你好"); } } }; </script> <style> #app { font-family: "Avenir", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
经过在methods中增长一个方法控制写在Vue原型上的$toast对toast组件进行操做。
这样toast组件的编写过程就结束了,能够看到一开始gif图里的效果。
通过上一个插件的讲解,这一部分就不会那么细致了,毕竟大多数都没有什么不一样,我只指出不同的地方。
<template> <div class='wrapper' v-if="isshow"> <div class='loading'> <img src="./loading.gif" alt="" width="40" height="40"> </div> </div> </template> <script> export default { props: { duration: { type: String, default: "1s" //默认1s }, isshow: { type: Boolean, default: false } }, data: function() { return {}; } }; </script> <style lang="scss" scoped> </style>
这个就只是一个模板,传入两个父组件的数据控制显示效果。
那再来看一下该插件的配置文件:
import LoadingComponent from './loading.vue' let Loading = {}; Loading.install = (Vue) => { Vue.component('loading', LoadingComponent) } export default Loading;
这个和taoat的插件相比,简单了不少,依然是一个空对象,里面有一个install方法,而后在全局注册了一个组件。
那介绍了这两种不一样的插件编写方法,貌似没有什么不同啊,真的是这样么?
来看一下完整的main.js和app.vue这两个文件:
// main.js import Vue from 'vue' import App from './App' // import router from './router' import Toast from './components/taost' import Loading from './components/loading' Vue.use(Toast) Vue.use(Loading) Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ // router, render: h => h(App) }).$mount('#app')
// app.vue <template> <div id="app"> <loading duration='2s' :isshow='show'></loading> <!-- <button @click="show = !show">显示/隐藏loading</button> --> <button @click="toast">显示taost弹出框</button> </div> </template> <script> export default { name: "app", data() { return { show: false }; }, methods: { toast() { this.$toast("你好"); } } }; </script> <style> #app { font-family: "Avenir", Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
能够看出来,loading是显示的写在app.vue模板里的,而toast并无做为一个组件写入,仅仅是经过一个方法控制显示。
来看一下html结构和vue工具给出的结构:
看出来了么,toast插件没有在挂载点里面,而是独立存在的,也就是说当执行
vue.use(toast)
以后,该插件就是生成好的了,以后的全部操做无非就是显示或者隐藏的问题了。