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

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

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


我们可以有两种方式去实现:

1. 定义一个 loading 的组件, 然后在每一个需要显示 loading 效果的组件中手动的导入

2. 使用自定义指令(v-loading)去动态的添加 loading 效果

显然第二种方案是要比第一种方案优雅的

如果给每一个需要添加 loading 效果的组件都去手动导入注册的话, 这个行为未免有点繁琐

首先我们需要明确, 我们需要在一个 DOM 元素中插入一个 loading 的效果

且这个 loading 效果并不是一尘不变的, 在当组件数据更新的之后; 我们需要将 loading 效果进行移除


第一步: 

创建 loading.vue 公共的基础组件

此组件需要向外暴露一个 setTitle 方法, 用于动态修改 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 函数中需要传入两个参数:

1. 自定义指令名称

2. 配置项

所以, 我们还需要创建 v-loading 的配置文件(directive)

然后在 main.js 入口文件中进行注册

// 导入v-loading的配置项
import loadingDirective from '@/components/base/loading/directive'

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

第三步:

现在我们就需要去封装 loadingDirective 这一个配置项 

1. 首先导入 loading 组件

2. 导入 createApp 函数

3. 定义 loadingDirective 对象, 此对象中有两个成员 mounted(组件挂载时) 和 updated(组件更新时)

4. 先完成 mounted 函数的逻辑

import loading from './loading.vue'
import { createApp } from 'vue'

const loadingDirective = {
  mounted (el, binding) {
    // 创建根组件
    const app = createApp(loading)
    // 获取vue实例
    const instance = app.mount(document.createElement('div'))
    // 因为在添加方法内部需要使用到instance, 但是instance是一个局部变量
    // 那么就添加到el对象中
    el.instance = instance
    if (binding.value) {
      append(el)
    }
  }
}

// 添加元素方法
function append (el) {
  el.appendChild(el.instance.$el)
}

5. 组件数据更新的时候, 需要执行 updated 钩子函数

import loading from './loading.vue'
import { createApp } from 'vue'

const loadingDirective = {
  mounted (el, binding) {
    const app = createApp(loading)
    const instance = app.mount(document.createElement('div'))
    el.instance = instance
    if (binding.value) {
      append(el)
    }
  },
  updated (el, binding) {
    // 如果旧值不等于新值的话, 就需要动态的执行添加和移除元素方法
    if (binding.oldValue !== binding.value) {
      // 组件没有再次初始化的前提下, 何时会再次显示loading效果
      // 比如, 分页组件, 当当前页码值发生变化的时候需要重新请求数据; 渲染数据
      binding.value ? append(el) : remove(el)
    }
  }
}

function append (el) {
  el.appendChild(el.instance.$el)
}

// 移除元素方法
function remove (el) {
  el.removeChild(el.instance.$el)
}

export default loadingDirective

配置文件写到这里, 一个 v-loading 自定义指令就完成了

但是我们还可以做一些优化:

1. 不要依赖父元素的定位, 因为 loading 效果需要在父元素的正中间显示

2. 可以动态的设置 loading 效果的文案


先来完成第一个优化

我们将对一些 dom 的操作提取到一个 dom.js 文件中

export function addClass (el, className) {
  // 不要对一个元素重复添加class类
  if (!el.classList.contains(className)) {
    el.classList.add(className)
  }
}

export function removeClass (el, className) {
  // 如果删除一个不存在的class类是不会报错的
  el.classList.remove(className)
}

然后我们需要对 loadingDirective 配置项进行修改

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

// 在css中定义一个class类
const relativeCls = 'g-relative'

const loadingDirective = {
  mounted (el, binding) {
    const app = createApp(loading)
    const instance = app.mount(document.createElement('div'))
    el.instance = instance
    if (binding.value) {
      append(el)
    }
  },
  updated (el, binding) {
    if (binding.oldValue !== binding.value) {
      binding.value ? append(el) : remove(el)
    }
  }
}

function append (el) {
  // 动态的获取当前el元素的style
  const style = getComputedStyle(el)
  // 判断是否存在以下几种定位
  if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
    addClass(el, relativeCls)
  }
  el.appendChild(el.instance.$el)
}

function remove (el) {
  // 组件数据更新的时候需要移除掉class类
  removeClass(el, relativeCls)
  el.removeChild(el.instance.$el)
}

export default loadingDirective

第二个优化:

在组件挂载的时候, 去动态的给 loading 效果添加文案

这里需要使用到 loading 组件向外暴露的 setTitle 函数

然后通过 binding.arg 方法获取传入的数据, 再调用 setTitle 进行动态的设置

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

const relativeCls = 'g-relative'

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) {
      append(el)
    }
  },
  updated (el, binding) {
    // 组件更新的时候也是一样
    const title = binding.arg
    if (typeof title !== 'undefined') {
      el.instance.setTitle(title)
    }
    if (binding.oldValue !== binding.value) {
      binding.value ? append(el) : remove(el)
    }
  }
}

function append (el) {
  const style = getComputedStyle(el)
  if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
    addClass(el, relativeCls)
  }
  el.appendChild(el.instance.$el)
}

function remove (el) {
  removeClass(el, relativeCls)
  el.removeChild(el.instance.$el)
}

export default loadingDirective

然后看一下在需要 v-loading 的组件使用

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

<script>
export default {
  components: { Slider, Scroll },
  data () {
      loadingText: '正在载入...'
    }
  },
  computed: {
    loading () {
      return // 动态判断loading效果是否显示
    }
  }
}
</script>

我们还可以将 loadingDirective 这一个配置项兼容不同的自定义指令, 当然前提就是共用这一个配置项的自定义指令的业务应该是差不多

首先我们得封装一个函数, 这一个函数的返回值是一个对象(也就是配置项)

然后这一个函数需要接收不同的 vue 组件, 来在 el 上生成不同的实例

所以我们需要在 update , append 和 remove 方法执行的时候, 区分 el 上不同的实例

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

const relativeCls = 'g-relative'

export default function createLoadingLikeDirective (comp) {
  return {
    mounted (el, binding) {
      const app = createApp(comp)
      const instance = app.mount(document.createElement('div'))

      // 区分不同的组件实例
      const name = comp.name
      if (!el[name]) {
        el[name] = {}
      }
      el[name].instance = instance

      const title = binding.arg
      if (typeof title !== 'undefined') {
        instance.setTitle(title)
      }

      if (binding.value) {
        append(el)
      }
    },
    updated (el, binding) {
      const name = comp.name
      const title = binding.arg
      if (typeof title !== 'undefined') {
        el[name].instance.setTitle(title)
      }

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

  function append (el) {
    const name = comp.name
    const style = getComputedStyle(el)
    if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
      addClass(el, relativeCls)
    }
    el.appendChild(el[name].instance.$el)
  }

  function remove (el) {
    const name = comp.name
    removeClass(el, relativeCls)
    el.removeChild(el[name].instance.$el)
  }
}

那么 loading/directive.js 中的代码需要做修改

import loading from './loading.vue'
import createLoadingLikeDirective from '@/assets/js/create-loading-like-directive'

export default createLoadingLikeDirective(loading)
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值