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
