【vue3】封装函数配合directive方法实现自定义V-指令

7545次阅读 547人点赞 作者: WuBin 发布时间: 2021-10-13 14:09:48
扫码到手机查看

封装一个函数

上一篇我们讲过《Vue3开发一个v-loading的自定义指令》 

通过项目的进一步开发,我们发现,我们需要多个指令,而且组件逻辑都与v-loading相似,那么相似的部分,我们就可以封装一下。

目录结构

src----assets
              |-----js
                   |----create-loading-like-directive.js   创建类似v-loading的自定义指令
                   |----dom.js  专门存放dom操作的函数
              |-----components
                       |----base
                                |--- no-result
                                        |---no-result.vue  请求无数据时展示的组件
                                        |---directive.js  将no-result组件转化为指令

create-loading-like-directive.js

 作用,创造一个类似于v-loading一样的指令。

 注意使用的时候需要在main.js中进行注册(见《二》)

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

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

/*
* 参数如果传一个loading组件那么就创建v-loading指令,如传no-result那么就创建v-no-result指令
* Comp 就是import进入的组件
* */
export default function createLoadingLikeDirective(Comp) {
  const directive = {
    /*
     主要写一些钩子函数 在钩子中去实现逻辑
     指令主要是将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(Comp)
      /*
      拿到它的实例,挂载到动态创建的DOM上,vue开发是支持多实例的,可以创建多个实例
      因为创建的元素没挂载到BODY上,实际也没有完成dom层的挂载,
      目的是创建出来的实例的DOM对象要挂载到el上(指令所在的DOM)
       */
      const instance = app.mount(document.createElement('div'))
      /*
      * 拿到组件的名称,当多个指令应用于一个元素上时,后面的会覆盖前面的el.instance,导致删除失败
      * 通过对象多一个维度,解决后面创建的值覆盖前面值的问题 小技巧谨记
      * */
      const name = Comp.name
      // 检测如果el中没有这个字段 那么创建一个对象
      if (!el[name]) {
        el[name] = {}
      }
      /*
      因为instance在mounted中只创建一次,但是之后会经常用到,要保留起来,
      如果要在其他的钩子函数也要访问它的话就存在el对象上
      这样操作在其他钩子中也可以获取到这个实例
      不同的组件,使用不同的el[name],呢么其各自的instance就不同了,比如el['loading'].ins, el['no-result'].ins
      */
      el[name].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
      const name = Comp.name
      // 如果参数不是空 执行实例中的方法
      if (typeof title !== 'undefined') {
        el[name].instance.setTitle(title)
      }
      // 如果loading前后值不一致
      if (binding.value !== binding.oldValue) {
        // 如果是true那么就插入否则删除
        binding.value ? append(el) : remove(el)
      }
    }
  }
  return directive

  // 元素挂载的操作
  function append(el) {
    const name = Comp.name
    // 根据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[name].instance.$el)
  }

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

这里应用到了一个小技巧,请见介绍如何使用的部分。

DOM.js

// 存放比较通用的dom操作
// el是一个DOM对象
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-指令

在组件的directive.js中:

import NoResult from './no-result'
import createLoadingLikeDirective from '@/assets/js/create-loading-like-directive'

const noResultDirective = createLoadingLikeDirective(NoResult)

export default noResultDirective

在main.js中进行注册

import { createApp } from 'vue'
import App from './App.vue'
import lazyPlugin from 'vue3-lazy'

// 注册指令 引入注册的文件
import loadingDirective from '@/components/base/loading/directive'
import noResultDirective from '@/components/base/no-result/directive'

/* 注册的时候使用directive(‘指令名称’, 指令对象)
 因为叫v-loading所以这里传入Loading,
* directive('loading', loadingDirective)
全局注册后这个app下就可以全局使用v-loading了
* */
createApp(App).use(store).use(router).use(lazyPlugin, {
  // 配置默认加载的图片 使用webpack的require语法,它会自适应选择图片加载的方式外链或者base64
  loading: require('@/assets/images/default.png')
}).directive('loading', loadingDirective).directive('no-result', noResultDirective).mount('#app')

具体使用

比如在div上:

<div
      class="list"
      v-loading="loading"
      v-no-result:[noResultText]="noResult"
>...</div>

v-no-result上的 :[noResultText] 是指令参数 固定写法。如果是多个参数,noResultText就传入一个数组。

props: {
    title: String,
    // v-loading不是每次都通过数据,也可以通过赋值去改变状态
    loading: Boolean,
    noResultText: {
      type: String,
      default: '抱歉,没找到可以播放的歌曲'
    }
  },
computed: {
    // 显示是否有内容,当无内容的时候显示
    noResult() {
      // 当loading加载结束并且歌单列表为空 就返回true
      return !this.loading && !this.songs.length
    },
}

针对于组件的参数,推荐在props中进行定义,方便可以从组件外部修改状态,同时赋于默认值。

小技巧

通过上面例子,我们可以看到,可能会在一个元素上同时使用两个指令的情况,那么这时就需要对封装的函数特别注意了。

const name = Comp.name

if (!el[name]) {
        el[name] = {}
}
el[name].instance = instance

在封装的时候,我们获取了组件的名称,并且使用组件名称作为el对象的一个字段。这时因为,如果按照以前的写法:

el[name].instance = instance

当两个指令同时用的时候,后面的一个会覆盖掉之前的,导致在v-loading指令执行update的时候,实际删除的是no-result组件的元素,而那时它其实还没有创建,导致报错。因此在遇到这种清况时,通过动态添加字段,扩展维度,让不同的组件分别保存在对象的不同字段中。避免冲突。

希望在以后的工作中,大家可以充分利用这个小技巧。

还有要注意的

还有要注意的一点就是,使用同样的封装函数,对于组件,必须要有类似的要求。比如:

 el[name].instance.setTitle(title)

setTitle方法,必须同类型的组件都要实现这个方法。

no-result组件

<template>
  <div class="no-result">
    <div class="no-result-content">
      <div class="icon"></div>
      <p class="text">{{title}}</p>
    </div>
  </div>
</template>

<script type="text/ecmascript-6">
  export default {
    name: 'no-result',
    data() {
      return {
        title: '抱歉,没有结果'
      }
    },
    methods: {
      setTitle(title) {
        this.title = title
      }
    }
  }
</script>

<style lang="scss" scoped>
  .no-result {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate3d(-50%, -50%, 0);
    .no-result-content {
      text-align: center;
      .icon {
        width: 86px;
        height: 90px;
        margin: 0 auto;
        @include bg-image('no-result');
        background-size: 86px 90px;
      }
      .text {
        margin-top: 30px;
        font-size: $font-size-medium;
        color: $color-text-d;
      }
    }
  }
</style>
点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:自定义v-指令
推荐阅读
  • uniapp实现被浏览器唤起的功能

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

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

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

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

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

    几个高级前端常用的API

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

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

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

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

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

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

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

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

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

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

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

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

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

    Vue+html2canvas截图空白的问题

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

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

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

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

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