elementui 中 loading 组件源码解析

时间:2021-1-8 作者:admin

开始前的准备

  1. 将 elementui 的 master 分支完整的 clone 下来,传送门

  2. 执行npm run dev下载好依赖,如果总是执行失败,一般都是 nodesass 为下载成功导致的,这时需要提前下载好 nodesass 然后再执行npm run dev:play就好.
    成功之后打开 localhost:8085

  3. 代码定位,因为执行的是npm run dev:play,具体代码如下

"dev:play": "npm run build:file && cross-env NODE_ENV=development PLAY_ENV=true webpack-dev-server --config build/webpack.demo.js"

然后我们查找build/webpack.demo.js,然后找到如下代码

entry: isProd ? {
    docs: './examples/entry.js'
  } : (isPlay ? './examples/play.js' : './examples/entry.js'),

很明显,我们的入口在./examples/play.js,然后我们根据这个文件找到 examples/play 文件夹下的 vue 文件,那么我们的准备工作就算是完成了。

正式开始

文档分析

该组件有两种调用方式,一种是指令(通过 v-loading 去调用),一种是通过 Vue 实例方法调用(通过 this.$loading 去调用)

目录结构

loading 文件夹下面的 index 文件代码如下

import directive from './src/directive';
import service from './src/index';

export default {
  install(Vue) {
    Vue.use(directive);
    Vue.prototype.$loading = service;
  },
  directive,
  service
};

根据以上代码,我们就可以知道支持指令来调用的在./src/directive文件中,支持添加 Vue 实例方法调用的在./src/index文件中。

添加 Vue 实例方法

  1. 查看源文件
    通过目录结构,我们已经知道两种方法调用的源文件其实就是loading.vue文件,另外两个只是在此基础上做的扩展
    源码如下:

<template>
  <transition name="el-loading-fade" @after-leave="handleAfterLeave">
    <div
      v-show="visible"
      class="el-loading-mask"
      :style="{ backgroundColor: background || '' }"
      :class="[customClass, { 'is-fullscreen': fullscreen }]">
      <div class="el-loading-spinner">
        <svg v-if="!spinner" class="circular" viewBox="25 25 50 50">
          <circle class="path" cx="50" cy="50" r="20" fill="none"/>
        </svg>
        <i v-else :class="spinner"></i>
        <p v-if="text" class="el-loading-text">{{ text }}</p>
      </div>
    </div>
  </transition>
</template>

<script>
  export default {
    data() {
      return {
        text: null,
        spinner: null,
        background: null,
        fullscreen: true,
        visible: false,
        customClass: ''
      };
    },

    methods: {
      handleAfterLeave() {
        this.$emit('after-leave');
      },
      setText(text) {
        this.text = text;
      }
    }
  };
</script>

源码内容比较简单,里面就是 transition 动画包裹的一个绝对定位的盒子,里面装着默认的 svg 以及自定位文字,
接下来是扩展部分,内容在 src 文件夹下的 index.js 。因为其中有很大一部分代码在处理样式及层级关系,所以被我剔除掉了,下面只展示核心代码:

import loadingVue from './loading.vue';

const LoadingConstructor = Vue.extend(loadingVue);
        const defaults = {
            text: null,
            fullscreen: true,
            body: false,
            lock: false,
            customClass: ''
        };
        let fullscreenLoading;//用来保存弹窗实例
        // 关闭弹窗的方法
        function afterLeave(instance, callback, speed = 300, once = false) {
            let called = false;
            const afterLeaveCallback = function () {
                if (called) return;
                called = true;
                if (callback) {
                    callback.apply(null, arguments);
                }
            };

            setTimeout(() => {
                afterLeaveCallback();
            }, speed + 100);
        };

        LoadingConstructor.prototype.close = function () {
            // 清除弹窗实例
            if (this.fullscreen) {
                fullscreenLoading = undefined;
            }
            afterLeave(this, _ => {
                // 结束后删除节点
                if (this.$el && this.$el.parentNode) {
                    this.$el.parentNode.removeChild(this.$el);
                }
                this.$destroy();
            }, 300);
            this.visible = false;
        };
        let service = (options = {}) => {
            // 合并options,源码中用的是 Object.assign 的 profill
            options = Object.assign({}, defaults, options);
            if (typeof options.target === 'string') {
                options.target = document.querySelector(options.target);
            }
            options.target = options.target || document.body;
            if (options.target !== document.body) {
                options.fullscreen = false;
            } else {
                options.body = true;
            }
            // 覆盖整个body的loading只能有一个
            if (options.fullscreen && fullscreenLoading) {
                return fullscreenLoading;
            }
            let instance = new LoadingConstructor({
                el: document.createElement('div'),
                data: options
            });
            // 如果没有 target ,那么弹窗将会直接挂载在body上
            let parent = options.body ? document.body : options.target;
            parent.appendChild(instance.$el);
            Vue.nextTick(() => {
                instance.visible = true;
            });
            // 将实例赋值给 fullscreenLoading
            if (options.fullscreen) {
                fullscreenLoading = instance;
            }
            return instance;
        }

        Vue.prototype.$loading = service

最后再验证一下

<div id="app">
        <!-- 因为loading组件是绝对定位,所以在扩展时会在父节点加上 el-loading-parent--relative-->
        <div id="loading" class="el-loading-parent--relative"></div>
    </div>

实例化

new Vue({
            el: '#app',
            data: function () {
                return {
                    visible: true
                }
            },
            mounted() {
                const loading = this.$loading({
                    lock: true,
                    text: 'Loading',
                    spinner: 'el-icon-loading',
                    background: 'rgba(0, 0, 0, 0.7)',
                    target: "#loading"
                })
                setTimeout(() => {
                    loading.close();
                }, 2000);
            }
        })

效果图如下

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。