Vue3开发一个v-loading的自定义指令

推荐 15926次阅读 1269人点赞 作者: WuBin 发布时间: 2021-07-02 15:58:35
扫码到手机查看

效果图

我又更新了一篇:《封装一个自定义指令实现函数》 将v-指令的实现进行了封装。

要实现在div元素上添加v-loading指令,即可出现加载图,如下所示(仅展示基础结构 下面还会对其进行更改):

<div class="recommend" v-loading="loading">...</div>
computed: {
    loading() {
      // 当没有轮播和推荐列表的时候返回false
      return !this.sliders.length && !this.albums.length
    }
}

loading组件

在src/components/base/loading下新建loading.vue文件,如下:

<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 type="text/ecmascript-6">
// 使用的时候 在容器上添加 div v-loading="true"即可实现loading效果 无需import
export default {
  name: 'loading',
  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>

自定义指令执行逻辑

在src/components/base/loading下新建directive.js:

// 自定义v-loading指令
import { createApp } from 'vue'
import Loading from './loading'
import { addClass, removeClass } from '@/assets/js/dom'

// 该样式添加在了assets/scss/base.scss中,.g-relative{position: relative}
const relativeCls = 'g-relative'

const loadingDirective = {
  // 主要写一些钩子函数 在钩子中去实现逻辑
  /*
  指令主要是将loading组件生成的DOM动态插入到指令作用的DOM对象上(v-loading=true),
  如果v-loading=false那么就删除动态插入的
   */
  // 指令挂载时的钩子函数
  mounted(el, binding) {
    /* 
    el指向指令所在的dom 如 <div v-loading="true" id="box"> 那么el就是#box 
    binding.value就是代表的true
    */
    // 判断v-loading值为true动态插入到指令作用的节点下
    /* 
    如果创建组件对应的dom?先用这个loading组件新建一个vue实例(app对象),
    然后再动态取挂载,就会产生一个实例,在实例中拿到它的DOM对象
    */
    const app = createApp(Loading)
    // 拿到它的实例,挂载到动态创建的DOM上,vue开发是支持多实例的,可以创建多个实例
    /* 
    因为创建的元素没挂载到BODY上,实际也没有完成dom层的挂载,
    目的是创建出来的实例的DOM对象要挂载到el上(指令所在的DOM)
    */
    const instance = app.mount(document.createElement('div'))
    /* 
    因为instance在mounted中只创建一次,但是之后会经常用到,要保留起来
    如果要在其他的钩子函数也要访问它的话就存在参数的el对象上
    这样操作在其他钩子中也可以获取到这个实例
    */
    el.instance = instance
    // 通过binding.arg拿到动态参数,如果组件中有多个参数可以考虑传进来的是一个数组
    const title = binding.arg
    // 如果参数不是空 执行实例中的方法
    if (typeof title !== 'undefined') {
      instance.setTitle(title)
    }
    // binding.value就是代表指令传递的值
    if (binding.value) {
      append(el)
    }
  },
  // 当组件更新的时候执行,因为指令不是一成不变的
  // 比如由v-loading=true变为v-loading=false 就会执行
  updated(el, binding) {
    // 通过binding.arg拿到动态参数
    const title = binding.arg
    // 如果参数不是空 执行实例中的方法
    if (typeof title !== 'undefined') {
      el.instance.setTitle(title)
    }
    // 如果loading前后值不一致
    if (binding.value !== binding.oldValue) {
        // 如果是true那么就插入否则删除
        binding.value ? append(el) : remove(el)
    }
  }
}

// 元素挂载的操作
function append(el) {
  // 根据loading组件样式,是使用absolute,而当el不是fixed或retaive时候给其动态添加定位属性
  const style = getComputedStyle(el)
  // 判断el的样式中有无定位,===-1就是没有 希望v-loading不受样式限制
  if (['absolute', 'fixed', 'relative'].indexOf(style.position) === -1) {
    addClass(el, relativeCls)
  }

  // 因为loading组件生成的实例instance已经赋值给el.instance属性上了,所以在这里可以直接通过el拿到
  // el.instance.$el就是loading组件的DOM对象
  el.appendChild(el.instance.$el)
}

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

// 如果要在全局中使用,就在main.js中引入并注册
export default loadingDirective

针对以上代码,有几点需要特别说明:

流程逻辑

v-loading指令主要是将loading组件生成的DOM动态插入到指令作用的DOM对象上(v-loading=true),如果v-loading=false那么就删除动态插入的。

指令使用时开始,主要包含两个钩子函数:mounted和updated,mounted是在注册的时候运行,且只会运行一次;updated会在v-loading=“xx”值改变的时候执行。

mounted(el, binding) {...}

el指向指令所在的dom,如 <div v-loading="true" id="box"> 那么el就是#box。而binging我们也来打印看一下:

其中我们在代码中用到的有3处:

  • arg:通过binding.arg拿到动态参数,如果组件中有多个参数可以考虑传进来的是一个数组,动态参数就是在使用时:
<div class="recommend" v-loading:[loadingText]="loading">
data() {
    return {
      sliders: [],
      albums: [],
      loadingText: '正在载入...'
    }
  },
computed: {
    loading() {
      // 当没有轮播和推荐列表的时候返回false
      return !this.sliders.length && !this.albums.length
    }
}

注意,v-loading:[loadingText]是固定写法,不是数组!!,主要用来向组件中传递参数,如果是多个参数,那么就传递一个数组或者对象。

  • value:就是v-loading="loading" 中的loading的值
  • oldValue:v-loading之前的值,如果不存在就是undefined

保存组件实例的小技巧

使用loading组件再创建一个vue实例,然后将实例保存到mounted(el)的el中(这个实例只会在mounted中创建一次),这样就可以在其他的钩子函数中也可以获取到这个实例了。

const app = createApp(Loading)
const instance = app.mount(document.createElement('div'))
el.instance = instance

应该考虑更广泛的应用场景

通过看样式代码我们发现,loading组件依赖于父元素有定位,那么如果父元素没有定位的情况下,是不是应该给父元素动态的添加一个相对定位的样式?所以在append的函数中:

const style = getComputedStyle(el)
 // 判断el的样式中有无定位,===-1就是没有 
// 没有就添加一个样式 addClass实现请见下方
if (['absolute', 'fixed', 'relative'].indexOf(style.position) === -1) {
    addClass(el, relativeCls)
}

执行组件实例中的方法

因为loading组件中暴漏了一个setTitle的方法,而el.instance中保存了这个组件的实例,于是乎:

el.instance.setTitle(title) // 执行组件中的方法

addClass和removeClass的实现

在assets/js/dom.js中,写2个公用的方法,用于添加删除class:

export function addClass (el, className) {
  // 如果当前元素样式列表中没有className
  if (!el.classList.contains(className)) {
    el.classList.add(className)
  }
}

export function removeClass (el, className) {
  el.classList.remove(className)
}

v-loading的注册与应用

全局注册

在main.js中:

import loadingDirective from '@/components/base/loading/directive'
createApp(App).directive('loading', loadingDirective).mount('#app')

注册的时候使用directive(‘指令名称’, 指令对象)

因为叫v-loading所以这里传入loading, directive('loading', loadingDirective)全局注册后在这个app(对象)下就可以全局使用v-loading指令了

在组件中应用

<div class="recommend" v-loading:[loadingText]="loading">...</div>

data() {
    return {
      sliders: [],
      albums: [],
      loadingText: '正在载入...'
    }
},

computed: {
    loading() {
      // 当没有轮播和推荐列表的时候返回true
      // 当数据都有了之后返回false 删除加载层
      return !this.sliders.length && !this.albums.length
    }
}

v-loading:[loadingText] 再强调一遍,这里的[]不是数组!仅仅是vue3的一种语法。用于向binging.arg中传递动态参数!见(四)。如果像组件中传递多个值的话:

loadingText: [ ... ] 或者 loadingText:  { a: .., b: ..}

然后在loading中根据需要进行赋值即可。

附件下载

相关资料

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:v-loading,自定义指令
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    9315次阅读 600人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    4060次阅读 302人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    14265次阅读 931人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    13177次阅读 461人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    11801次阅读 943人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    15926次阅读 1269人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    14992次阅读 1220人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    29343次阅读 2304人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    25342次阅读 1952人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录