实现自定义指令v-loading

2,928 阅读3分钟

在项目中我们通常会使用一个loading的状态去控制页面渲染之前的状态,在Element-UI中只需要v-loading="loading",loading只需要绑定Boolean即可.但是其中是怎么实现的需要自己去主动思考。

自定义指令

v-for、v-if、v-model这些都是vue自带的指令,vue的实例主要用于数据绑定、事件监听、dom更新,这些指令主要就是去操作dom。 自定义指令分两种:

  1. 全局指令
  2. 局部指令

指令选项

1.钩子函数

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted:被绑定元素插入父节点时调用。

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。

componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind:只调用一次,指令与元素解绑时调用。

2.钩子函数参数

指令钩子函数会被传入以下参数:

el:指令所绑定的元素,可以用来直接操作 DOM。

binding:一个对象,包含以下 property: name:指令名

value:绑定的值 例如:v-my-directive="1 + 1" 中,绑定值为 2。

oldValue:绑定前的值。仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。

expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。

arg 传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。

modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }

实现v-load

首先在src文件夹下创建一个directive文件夹去存放loading相关的代码,在loading.vue文件中去写相关的样式操作

<template>
  <div v-show="visible" class="loading-wrap">
    <div class="loading-box">
      <div class="loading-add"></div>
      <div class="loading-txt">加载中...</div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      visible:false
    }
  }
}
</script>

<style lang="less" scoped>
.loading-wrap{
  white-space: nowrap;
}

.loading-box{
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%);
  font-size: 16px;
  white-space: nowrap;
  user-select: none;
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}

.loading-add{
  width: 30px;
  height: 30px;
  border: 2px solid #F53939 ;
  border-top-color: transparent;
  border-radius: 100%;
  animation: add infinite 0.75s linear;
}
@keyframes add {
  0% {
    transform: rotateZ(0);
  }

  100% {
    transform: rotateZ(360deg);
  }
}

.loading-txt {
  margin-top: 5px;
  color: #F53939 ;
}

</style> 

同级目录去创建一个loading.js

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

const Mask = Vue.extend(Loading);

const toggleLoading = (el, binding) => {
  if(binding.value) {
    Vue.nextTick(() => {
      // 控制loading组件显示
      el.instance.visible = true;
      // 插入到目标元素
      insertDom(el, el, binding)
    }) 
  }else{
    el.instance.visible = false;
  }
}

const insertDom = (parent, el) => {
  parent.appendChild(el.mask)
}

export default {
  // bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
  bind: function (el,binding,vnode) {
    // new Mask()的时候,把该组件实例挂载到一个div上
    const mask = new Mask({
      el: document.createElement('div'),
      data() {return {}}
    })
    el.instance = mask;
    el.mask = mask.$el;
    binding.value && toggleLoading(el,binding)
  },
  update: function (el, binding) {
    if(binding.oldValue !== binding.value) {
      toggleLoading(el, binding)
    }
  },
  unbind: function (el, binding) {
    el.instance && el.instance.$destroy()
  }
}

Vue.extend 返回一个构造器,new该构造器可以返回一个组件实例,new Mask()的时候,把该组件实例挂载到一个div上。 在bing函数先去打印一下el看一下都是什么:

2.png

发现是一个空的div,此时需要用一个变量去接住这个实例,el.instance = mask,- 接下来判断 value 是否为 true ,如果是 true 则执行 toggleLoading ,toggleLoading 方法用来控制是否显示 loading.vue 组件中的 visible变量,并且如果 value是true则插入到目标元素。

在directive文件夹下创建index.js

import loading from './loading';

export default {
  install(Vue) {
    Vue.directive("loading",loading)  
  }
}

main.js中去全局注册:

import loading from './directives/loading'
Vue.use(loading)

项目中去使用:

 <div v-load="true"></div>

让我们来来看一下效果:

3.png