前言
本文的研究思路是通过阅读Element源码,然后自动动手一步一步编写组件,完善其对应功能。
今天来研究一下Element的Loading组件是怎样写出来的。
基本实现
上测试代码,在element中,在标签上使用v-loading命令可以实现Loading组件显示隐藏的功能。在最基本的实现中我们先不搞这个特性,还是直接把el-loading标签放到测试页面里,先看下显示效果:
上测试代码:
<template>
<div>
<!-- 直接使用loading组件,先不采用v-loading这种高级形式 -->
<el-loading />
</div>
</template>
<script>
import ElLoading from '../../components/Loading/loading.vue'
export default {
name: 'LoadingShownPage',
components: {
ElLoading
}
}
上组件代码:
<template>
<transition name="el-loading-fade">
<div
class="el-loading-mask">
<div class="el-loading-spinner">
<svg class="circular" viewBox="25 25 50 50">
<circle class="path" cx="50" cy="50" r="20" fill="none"/>
</svg>
</div>
</div>
</transition>
</template>
<script>
export default {
}
</script>
上效果:
可以看到我们组件只需几个简单的标签,就可以实现效果。
使用v-loading命令控制Loading组件显示隐藏
在我们研究InputNumber中,第一次使用了自定义命令directive来实现为组件添加持续点击v-repeat-click事件。这一次要自定义一个v-loading命令。区别是这次不是局部的组件内注册,而是全局注册:
import Vue from 'vue';
import Loading from './loading.vue';
const Mask = Vue.extend(Loading);
Vue.directive('loading', {
bind: function(el, binding, vnode) {
// el 指令所绑定的元素,可以用来直接操作 DOM。
// binding 绑定值相关的对象。如binding.value就是v-loading等于的值
// vnode当前虚拟节点
const mask = new Mask({
el: document.createElement('div'),
})
console.log('mask',mask)
el.instance = mask; // 将loading组件实例放到el.instance上
el.mask = mask.$el;
el.appendChild(el.mask); // 将loading组件的dom加入到绑定的组件dom上
// 控制loaing显示隐藏
binding.value && toggleLoading(el, binding);
}
update: function(el, binding) {
if (binding.oldValue !== binding.value) {
toggleLoading(el, binding);
}
},
})
这样只要在页面引入有此代码的文件,使其执行。就全局支持v-loading命令了。
接下来实现控制显示隐藏loading的逻辑。
先为loading组件加名为visible的data。然后在根div上添加v-show="visible"。然后在toggleLoading控制visible。
const toggleLoading = (el, binding) => {
// el.instance就是mask实例就是我们的loading组件
if (binding.value) {
// v-loading=ture
el.instance.visible = true;
} else {
// v-loading=false
el.instance.visible = false;
}
}
现在我们改动v-loading的true/false时,loading就动态显示隐藏了。
vue插件方式引入Loading组件
现在我们使用的是直接在测试页面里引入的方式:
// directive.js是上述代码所在的文件
import '../../components/Loading/directive.js'
现在我们用插件的方式来做。
在directive.js中:
const loadingDirective = {};
loadingDirective.install = Vue => {
// 将上面写的内容放在这里
...
}
export default loadingDirective
在main.js中:
import loadingDirective from './components/Loading/directive.js'
Vue.use(loadingDirective);
经测试,v-loading依然好用,Loading组件分为已指令方式和服务方式,指令方式的原理到此已基本弄懂。
支持区域加载
为v-loading绑定的元素添加样式el-loading-parent--relative。就可以实现区域加载,原理就是让找个元素的position: relative;
if (el.originalPosition !== 'absolute' && el.originalPosition !== 'fixed') {
addClass(el, 'el-loading-parent--relative');
}
效果:
支持全局加载
在测试页面写v-loading.fullscreen="loading"。加上了fullscreen修饰符。
然后在toggleLoading方法中写:
if (binding.value) {
// v-loading=ture
el.instance.visible = true;
if (binding.modifiers.fullscreen) {
addClass(el.mask, 'is-fullscreen');
}
}
如果有fullscreen修饰符,添加is-fullscreen样式。原理就是让loading组件的position: fixed;。这样又全屏加载loading了。
用服务方式使用Loading
我们还可以通过下面这种方式来控制loading
// 开启loading
const loading = this.$loading();
// 关闭loading
loading.close();
写一个service.js文件
import Vue from 'vue';
import loadingVue from './loading.vue';
import { addClass } from '../../utils/dom'
const LoadingConstructor = Vue.extend(loadingVue);
LoadingConstructor.prototype.close = function() {
this.visible = false;
}
const loadingService = () => {
let instance = new LoadingConstructor({
el: document.createElement('div'),
});
if (instance.originalPosition !== 'absolute' && instance.originalPosition !== 'fixed') {
addClass(parent, 'el-loading-parent--relative');
}
document.body.appendChild(instance.$el);
instance.visible = true;
return instance;
}
export default loadingService;
在main.js中使用loadingService:
import loadingService from './components/Loading/service.js'
Vue.prototype.$loading = loadingService;
通过上面这段代码即可实现。这个到处的用服务方式使用Loading。
总结
通过研究Loading组件,我们可以接触到很多知识点。包括自定义命令、自定义命令修饰符的使用,自定义插件,给Vue.prototype挂载对象、Vue.extend声明vue组件等。
其他组件源码研究: