记录一个实习菜鸟写图片预览组件的艰辛道路~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, // }); }, }