element-ui深入浅出 v-loading指令

20,156 阅读2分钟

想必用过 element-ui 来开发中后台项目的同学都知道,其内置的 v-loading 指令是非常友好,只需要提供一个 Boolean 值就能实现加载动画的一个指令,如果使用过 element-ui,但没有使用过 v-loading 指令的同学,不妨先了解一下基本用法

本文会通过 源码剖析思路分析以及简单实现三个步骤,来简单呈现其实现的原理。

源码剖析

首先打开 element-ui 项目目录,定位到 v-loading 主文件


import directive from './src/directive'; // loading指令实现
import service from './src/index'; // loading服务方式实现

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

此文件对外暴露了三个属性,分别是 install函数directive指令实现以及service服务方式实现

此文件会被 element组件入口文件 引入, 并且把 directive指令 全局注册了一遍以及在 Vue 原型上扩展了 $loading 方法

// line 156
Vue.use(Loading.directive);

// line 163
Vue.prototype.$loading = Loading.service;

Loading 指令实现

Vue.js 的插件应该有一个公开方法 install。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

思路分析

其实 v-loading 实现思路很简单,且听我一一道来。

Vue.directive 内置了五个钩子函数 bind(绑定触发)inserted(插入触发)update(更新触发)componentUpdated(组件更新触发)unbind(解绑触发)

当需要绑定 v-loading 生成好后,我们可以根据绑定的 Boolean 值,来控制显隐

那么如何生成loading效果遮罩层呢?element-ui的做法是 利用Vue.extend扩展loading组件,实时计算其样式值,并且把扩展的实例挂载到钩子函数的el参数中,以达到改变组件状态的目的

简单实现

接下来我将简单实现一遍 v-loading 的核心功能,来帮助大家更好的掌握

首先,我们需要定义一个 loading 组件:

<template>
  <transition name="loading-fade">
    <div
      v-show="visible"
      class="loading-mask"
    >
      loading
    </div>
  </transition>
</template>

<script>
export default {
  name: 'Loading',
  data() {
    return {
      visible: false
    };
  }
};
</script>

注意看这里 visible,这个属性就是来控制整个loading组件显隐的

当我们指令绑定之后,就需要对绑定的 value 值进行监听

import Vue from 'vue';
import Loading from './loading';

// Loading构造函数
const Mask = Vue.extend(Loading);

const loadingDirective = {};

loadingDirective.install = Vue => {
  // 切换组件状态函数
  const toggleLoading = (el, binding) => {
    if (binding.value) {
      Vue.nextTick(() => {
        insertDom(el, el, binding);
      });
    }
    else {
      el.instance.visible = false;
    }
  };

  // 插入Loading
  const insertDom = (parent, el) => {
    parent.appendChild(el.mask);
    el.instance.visible = true;
  };

  Vue.directive('loading', {
    bind: function(el, binding) {
      /**
       * 这里把Loading组件实例挂载到el上,然后再把el传参到toggleLoading中判断
       */
      const mask = new Mask({
        el: document.createElement('div'),
        data: {}
      });
      el.instance = mask;
      el.mask = mask.$el;
      el.maskStyle = {};

      binding.value && toggleLoading(el, binding);
    },
  
    update: function(el, binding) {
      if (binding.oldValue !== binding.value) {
        toggleLoading(el, binding);
      }
    },
  
    unbind: function() {
        // destory
    }
  });
};

export default loadingDirective;

Loading 指令 的具体实现在此 文件