loading效果很常见,常见到我们任何一个项目中,都可以见到他的身影。今天就以loading作为切入口,唠叨一下vuejs的插件的写法。
看vuejs官方文档关于插件的说明,关于使用插件和写插件,Vue插件基本上都躲不开以下几种方案:
添加全局方法或者属性,如: vue-custom-element
添加全局资源:指令/过滤器/过渡等,如 vue-touch
通过全局 mixin 方法添加一些组件选项,如: vue-router
添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router
我们以loading效果为例,添加一个全局自定义指令的方式写一个插件。使用方式如:
<div v-loading="loading"></div>
依赖loading值,展示或者隐藏loading效果。loading值默认为false,不展示loading效果。
loading效果的模板其实很简单,甚至可以用唯一一个div标签完成,这里我们不强求怎么写loading模板,我们将模板定义在Loading.vue文件内。
在loading.js文件内真正写插件:
首先看一下Vue.use这个方法:
安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
也就是说我们在loading.js文件中export一个对象出去的时候,如果该对象是普通的对象,则需要在对象中添加一个install的方法,而如果导出一个函数对象,可以直接导出该函数。
就以当前的loading举例说明:
如果导出一个对象:
export default {
vm: null,
install: Vue => {
Vue.directive(Loading.name, {
// 真正的实现逻辑
})
},
...
}
而如果导出是一个函数,我们可以这么写:
export default Vue => {
Vue.directive(Loading.name, {
// 真正的实现逻辑
})
}
仔细观察一下,发现我们可以将install方法的函数体直接导出当做use的参数。
我们这里就以对象方式导出,主要目的是留一个vm属性,用于保存生成的loading节点,方便删除该节点。
在刚刚写这个插件的时候,不是插件不会写,恰恰是自定义指令的钩子函数使用不当,导致问题的产生。我们需要依赖指令的binding.value值来展示或者隐藏loading效果。基本上尝试了所有的钩子函数后,发现只有update是在所在的组件 VNode 更新时调用,也就是说binding.value的值发生变化,在update内部更新dom。
update (el, binding) {
const ele = window.getComputedStyle(el, null)
// 添加判断,如果当前元素的高度小于60 则loading固定在整个屏幕中间 否则固定在当前元素中间
LoadingComp.vm.$el.style.position = !binding.modifiers.fixed && (parseInt(ele['height']) < 60 ? 'fixed' : 'absolute')
binding.value ? el.appendChild(LoadingComp.vm.$el) : el.removeChild(LoadingComp.vm.$el)
}
需要稍作解释的时候,我们有时候绑定的v-loading的dom节点高度在完全没有数据的时候,可能完全没有高度可言,那么这里就将loading的position设置为fixed,固定在屏幕*。这里可以自行定义是否需要该判断条件。
binding.value ? el.appendChild(LoadingComp.vm.$el) : el.removeChild(LoadingComp.vm.$el)
这么一句,就是添加和删除当前的loading节点,有点类似于v-if的处理效果。
这里还缺少生成loading的DOM节点这一过程,是因为我认为其他的过程只要生成一次就可以了,而不会随着binding.value的值变化发生任何变化。所以需要在钩子函数inserted内实现:
inserted (el, binding) {
const elem = window.getComputedStyle(el, null)
el.style.position = elem['position'] === 'static' && 'relative'
LoadingComp.vm = new LoadingConstructor().$mount()
// 如果带修饰符fixed loading固定在屏幕中间
let val = binding.modifiers.fixed && 'fixed'
LoadingComp.vm.$el.style.position = val && val
}
主要实现的是,当前绑定v-loading的元素是否有定位,如果没有定位属性,则添加position:"relative"属性。并同时生成loading对象,注意这里仅仅是生成,并没有挂载到文档中。
添加的一行注释,主要多实现了一个功能,就是如果用户就非得相对整个屏幕固定loading,那么可以使用v-loading.fixed="loading"来达成这一效果。
这里其实可以引申出来,如果loading需要遮罩层的效果的话,也可以以类似的方法实现。
看看完整的代码吧!
import Vue from 'vue'
import Loading from './Loading.vue'
const LoadingConstructor = Vue.extend(Loading)
const LoadingComp = {
vm: null,
install: Vue => {
Vue.directive(Loading.name, {
inserted (el, binding) {
const elem = window.getComputedStyle(el, null)
el.style.position = elem['position'] === 'static' && 'relative'
LoadingComp.vm = new LoadingConstructor().$mount()
// 如果带修饰符fixed loading固定在屏幕中间
let val = binding.modifiers.fixed && 'fixed'
LoadingComp.vm.$el.style.position = val && val
},
update (el, binding) {
const ele = window.getComputedStyle(el, null)
// 添加判断,如果当前元素的高度小于60 则loading固定在整个屏幕中间 否则固定在当前元素中间
LoadingComp.vm.$el.style.position = !binding.modifiers.fixed && (parseInt(ele['height']) < 60 ? 'fixed' : 'absolute')
binding.value ? el.appendChild(LoadingComp.vm.$el) : el.removeChild(LoadingComp.vm.$el)
}
})
}
} export default LoadingComp