阅读 268

Element组件源码研究-Loading组件

前言

本文的研究思路是通过阅读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组件等。

码云:https://gitee.com/DaBuChen/my-element-ui/tree/loading-init

其他组件源码研究:

Element组件源码研究-Button

Element组件源码研究-Input输入框

Element组件源码研究-Layout,Link,Radio

Element组件源码研究-Checkbox多选框

Element组件源码研究-InputNumber 计数器

Element组件源码研究-Loading组件

Element组件源码研究-Message组件