当我们刷新页面或者是首次加载的时候, 如果后端数据请求比较慢的情况下; 页面是会出现白屏情况的

所以我们可以使用 v-loading 去优化一下, 增加用户的体验性

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

其组件中需要向外暴露出一个 setTitle 的函数, 用于来动态的去改变 loading 加载文字

这个组件主要是让 loading 的效果在页面的正中间去显示

<template>
  <div class="loading">
    <div class="loading-content">
      <img width="24" height="24" src="./loading.gif">
      <p class="desc">{{title}}</p>
    </div>
  </div>
</template>

<script>
export default {
  name: 'loadingCom',
  data () {
    return {
      title: '正在载入...'
    }
  },
  methods: {
    setTitle (title) {
      this.title = title
    }
  }
}
</script>

<style lang="scss" scoped>
  .loading {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate3d(-50%, -50%, 0);
    .loading-content {
      text-align: center;
      .desc {
        line-height: 20px;
        font-size: $font-size-small;
        color: $color-text-l;
      }
    }
  }
</style>

vue 给我们提供了一个 api (directive), 可以让我们去开发自定义的指令

其 api 中需要传入两个参数 app.directive('指令名称', 指令配置项)

下面我们需要做的是, 创建一个 js 文件作为 v-loading 指令的配置项

思路分析:

1. 首先导入 loading 组件和 createApp 方法

2. 创建一个 vue 实例, 将 loading 组件作为实例的根组件

3. 定义 v-loading 的配置项, 其中包含两个钩子函数(mounted和updated)

4. 在 mounted 钩子函数执行的时候, 动态的创建一个 DOM 对象; 然后将 app 挂载到这个 DOM 对象上面

5. 当 app 执行了 mount 方法之后, 可以得到做了对应逻辑处理的 vue 实例; 我们命名为 instance

6. 是否需要在目标元素(el)上面显示 loading 组件取决于 binding.value 的值

7. 判断 binding.value 如果为真, 就需要在 el 中插入先前创建的 DOM 元素(可以在 instance 上的 $el 中获取)

8. 组件更新时, 会执行 updated 钩子函数; 如果说 binding.value 的值不和原来 binding.oldValue 的相同, 就需要动态的去调用插入元素函数和移除元素函数

v-loading 指令还可以做一些优化

1. 在创建的 DOM 元素被挂载的时候, 可以动态的判断 el 的 style 样式是否满足"子绝父相"的定位条件; 如果不满足需要动态的添加 g-relative 类

2. 在目标组件挂载和更新阶段, 可以动态的去修改 v-loading 指令的显示文字

directive 给我们提供了一个钩子函数 arg , 可以给指令传参数

封装v-loading全局自定义指令-LMLPHP

import { createApp } from 'vue'
import loading from './loading.vue'
import { addClass, removeClass } from '@/assets/js/dom'

// 相对定位的类名
const relativeCls = 'g-relative'

// loading的配置项
const loadingDirective = {
  // 指令挂载时
  mounted (el, binding) {
    const app = createApp(loading)
    const instance = app.mount(document.createElement('div'))
    el.instance = instance

    // 动态添加参数
    const title = binding.arg
    if (typeof title !== 'undefined') {
      instance.setTitle(title)
    }

    if (binding.value) {
      // 动态添加loading元素
      append(el)
    }
  },
  // 组件更新时
  updated (el, binding) {
    const title = binding.arg
    if (typeof title !== 'undefined') {
      el.instance.setTitle(title)
    }

    if (binding.value !== binding.oldValue) {
      binding.value ? append(el) : remove(el)
    }
  }
}

// 给目标元素添加loading元素
function append (el) {
  // 获取目标元素的style样式
  const style = getComputedStyle(el)
  // 如果目标元素style样式中没有fixed, absolute或relative其中一个的话, 是需要动态添加的
  // 因为loading效果需要在页面中居中对齐
  if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
    addClass(el, relativeCls)
  }
  el.appendChild(el.instance.$el)
}

// 移除目标元素的loading元素
function remove (el) {
  removeClass(el, relativeCls)
  el.removeChild(el.instance.$el)
}

export default loadingDirective
// 把对DOM的操作函数都会封装到这一个js文件下面

// 给目标元素添加类名
export function addClass (el, className) {
  if (!el.classList.contains(className)) {
    el.classList.add(className)
  }
}

// 移除目标元素类名
export function removeClass (el, className) {
  // 这里就不用做判断, 如果移除一个不存在的类名也是不会报错的
  el.classList.remove(className)
}
body, html {
  line-height: 1;
  font-family: 'PingFang SC', 'STHeitiSC-Light', 'Helvetica-Light', arial, sans-serif, 'Droid Sans Fallback';
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  background: $color-background;
  color: $color-text;
}

// 需要添加relative样式的时候, 直接给对应的DOM元素动态添加class类
.g-relative {
  position: relative;
}

最后需要在 mian.js 中去全局注册这一个 v-loading 指令

// v-loading自定义指令
import loadingDirective from '@/components/base/loading/directive'

createApp(App).use(store).use(router).directive('loading', loadingDirective).mount('#app')

在组件中使用一下

<template>
  <div v-loading:[loadingText]="loading"></div>
</template>

<script>
export default {
  name: 'recommendCom',
  components: { slider, scroll },
  data () {
    return {
      // loading组件的文字
      loadingText: '正在加载中......'
    }
  },
  computed: {
    loading () {
      // 动态的判断loading值的改变
    }
  }
}
</script>

使用的时候, 我们可以直接在需要显示 loading 效果的元素中插入 v-loading 指令

这样要比手动的去引入, 然后通过 v-if 判断显示或不显示要优雅的很多

且如果多个组件都需要使用 loading 的话, 我们可以直接插入 v-loading 指令; 这样也方便的多

 封装v-loading全局自定义指令-LMLPHP

11-09 08:20