記錄一個實習菜鳥寫圖片預覽組件的艱辛道路~html
elementUI不少組件中使用了指令模式和服務模式,好比:loading
、message
...
<template> <div :v-loading.fullscreen="true">全屏覆蓋</div> </template>
const loading = this.$loading({ lock: true, text: 'Loading', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' });
打開node_modules
目錄,找到其下elementUI
目錄:vue
element-ui\src\index.js
文件中有一大坨組件註冊信息,重點找到咱們要找的loading...node
// ... // directive 指令裝載 Vue.use(Loading.directive) // prototype 服務裝載 Vue.prototype.$loading = Loading.service // ...
Vue.use()
這個指令是 Vue
用來安裝插件的,若是傳入的參數是一個對象,則該對象要提供一個 install
方法,若是是一個函數,則該函數被視爲 install
方法,在 install
方法調用時,會將 Vue
做爲參數傳入。git
先看看loading
/index.js
文件中是什麼鬼?
github
//引入指令文件和服務文件,directive爲指令模式文件,index.js爲服務模式文件 import directive from './src/directive'; import service from './src/index'; export default { //install方法註冊組件,不在贅述install的用法,star-pic-list圖片預覽組件文章中已經介紹過 install(Vue) { Vue.use(directive); //在vue的原型對象上註冊一個$loading的對象,這個$loading很是眼熟,看上面服務模式的使用,用到了this.$loading,源頭找到了 Vue.prototype.$loading = service; }, //引入的directive文件 directive, //引入的index.js文件 service };
v-loading
指令解析篇幅太長,其中咱們只取 fullscreen
修飾詞。element-ui
// 引入 .vue 文件 import Vue from 'vue' // 引入loading.vue基礎文件,裏面包含的是組件的基礎結構,如html結構,loading顯示的頁面結構都在這裏面 import Loading from './loading.vue' // 後面重點講解extend()構造器 // Vue.extend() 是vue構造器,它返回的是一個擴展實例構造器,也就是預設了部分選項的Vue實例構造器, // mask字面意思是面具,掩飾,能夠猜出來,這個經過Vue.extend(Loading)返回構造器應該是用於咱們loading加載時的遮罩層用的 // loading就是預設選項,就像vue示例中,有components,name,data,methods...好像有點明白了 const Mask = Vue.extend(Loading) const loadingDirective = {} // 還記得 Vue.use() 的使用方法麼?若傳入的是對象,該對象須要一個 install 屬性 loadingDirective.install = Vue => { // toggleLoading 方法看名字就是切換loading顯示和隱藏的嘛~ const toggleLoading = (el, binding) => { // 若綁定值爲 truthy 則插入 loading 元素 // binding 值是一個對象,有指令名、指令的綁定值、modifiers修飾符對象等等等等,具體的能夠去了解自定義指令相關內容 if (binding.value) { //binding.value是綁定的指令值 if (binding.modifiers.fullscreen) { 還記得咱們插入的指令嗎?:v-loading.fullscreen="true" , .fullscreen就是修飾符 insertDom(document.body, el, binding) //insertDom看名字就知道是插入新的元素 } // visible 是loading.vue data裏面定義的值 } else { el.instance.visible = false } } const insertDom = (parent, el, binding) => { // loading 設爲可見 el.instance.visible = true // appendChild 添加的元素若爲同一個,則不會重複添加 parent.appendChild(el.mask) } // 在此註冊 directive 指令 Vue.directive('loading', { bind: function(el, binding, vnode) { // 建立一個子組件,這裏和 new Vue(options) 相似 // 返回一個組件實例 const mask = new Mask({ el: document.createElement('div'), // 有些人看到這裏會迷惑,爲何這個 data 不按照 Vue 官方建議傳函數進去呢? // 其實這裏二者皆可 // 稍微作一點延展好了,在 Vue 源碼裏面,data 是延遲求值的 // 貼一點 Vue 源碼上來 // return function mergedInstanceDataFn() { // let instanceData = typeof childVal === 'function' // ? childVal.call(vm, vm) // : childVal; // let defaultData = typeof parentVal === 'function' // ? parentVal.call(vm, vm) // : parentVal; // if (instanceData) { // return mergeData(instanceData, defaultData) // } else { // return defaultData // } // } // instanceData 就是咱們如今傳入的 data: {} // defaultData 就是咱們 loading.vue 裏面的 data() {} // 看了這段代碼應該就不難理解爲何能夠傳對象進去了 data: { fullscreen: !!binding.modifiers.fullscreen } }) // 將建立的子類掛載到 el 上 // 在 directive 的文檔中建議 // 應該保證除了 el 以外其餘參數(binding、vnode)都是隻讀的 el.instance = mask // 掛載 dom // bind 只會調用一次,在bind 的時候給 el.mask 賦值,所以el.mask 所指的爲同一個 dom 元素 el.mask = mask.$el // 若 binding 的值爲 truthy 運行 toogleLoading binding.value && toggleLoading(el, binding) }, update: function(el, binding) { // 若舊不等於新值得時候(通常都是由 true 切換爲 false 的時候) if (binding.oldValue !== binding.value) { // 切換顯示或消失 toggleLoading(el, binding) } }, unbind: function(el, binding) { // 當組件 unbind 的時候,執行組件銷燬 el.instance && el.instance.$destroy() } }) } export default loadingDirective
關於extend()更多內容請參考 這裏,很是通熟易懂!
loading
服務方式調用原理直接看源碼:app
import Vue from 'vue' import loadingVue from './loading.vue' // 和指令模式同樣,建立實例構造器 const LoadingConstructor = Vue.extend(loadingVue) // 定義變量,若使用的是全屏 loading 那就要保證全局的 loading 只有一個 let fullscreenLoading // 這裏能夠看到和指令模式不一樣的地方 // 在調用了 close 以後就會移除該元素並銷燬組件 LoadingConstructor.prototype.close = function() { setTimeout(() => { if (this.$el && this.$el.parentNode) { this.$el.parentNode.removeChild(this.$el) } this.$destroy() }, 3000) } const Loading = (options = {}) => { // 若調用 loading 的時候傳入了 fullscreen 而且 fullscreenLoading 不爲 falsy // fullscreenLoading 只會在下面賦值,而且指向了 loading 實例 if (options.fullscreen && fullscreenLoading) { return fullscreenLoading } // 這裏就不用說了吧,和指令中是同樣的 let instance = new LoadingConstructor({ el: document.createElement('div'), data: options }) let parent = document.body // 直接添加元素 parent.appendChild(instance.$el) // 將其設置爲可見 // 另外,寫到這裏的時候我查閱了相關的資料 // 本身之前一直理解 nextTick 是在 dom 元素更新完畢以後再執行回調 // 可是發現可能並非這麼回事,後續我會繼續研究 // 若是乾貨足夠的話我會寫一篇關於 nextTick ui-render microtask macrotask 的文章 Vue.nextTick(() => { instance.visible = true }) // 若傳入了 fullscreen 參數,則將實例存儲 if (options.fullscreen) { fullscreenLoading = instance } // 返回實例,方便以後可以調用原型上的 close() 方法 return instance } export default Loading
directive.js
是指令模式文件,index.js
是服務模式文件,star-pic-preview.vue
是基礎單文件,包含了基礎的html
結構dom
我直接使用了指令,並無傳參,由於功能簡單,默認參數就是false函數
<img src="http://img5.imgtn.bdimg.com/it/u=3300305952,1328708913&fm=26&gp=0.jpg" v-pic-preview >
點擊出現遮罩層,圖片居中顯示預覽ui
<img src="http://img4q.duitang.com/uploads/item/201502/22/20150222191447_jdBYa.thumb.700_0.jpeg" @click="openImagePreview2" >
methods: { // 服務方式 openImagePreview2(e) { // 若是隻傳圖片 this.$picPreview(e.target.src); //若是傳複雜對象,能夠配置遮罩層的背景顏色等... // this.$picPreview({ // background: 'rgba(0, 0, 0, 0.7)', // imageUrl: e.target.src, // }); }, }