vue3 derictive 自定义指令 手把手带你写一个全局loading

在写一个H5项目,然后需要一个全局使用的 loading , 用插件也可以搞定,但是项目周期不怎么紧张,所以决定自己写一个。
写一个全局的 componnets 也行,但是需要在每个使用的页面进行导入并且注册,因为 loading 用到的地方会很多,所以我觉得需要更优雅的来搞定它 !!!

所以就选择使用自定义指令来实现,写指令麻烦点,但是写好以后用起来香嘛,全局注册一次,就可以随便使用啦!!!

OK 接下来就开始操作起来!

先看看loading的效果
loading.png

这是最终的效果,期望在页面未加载出来的时候上下左右居中显示,我不会上传gif图片hahaha , 所以看起来它是静止的,其实他是转圈圈的。

我要写一个这样的组件,在写组件这里我就不多做赘述,很基础的东西,我把代码贴上来

<template>
  <div class="container">
    <img src="@/assets/img/loading.gif" alt="" />
    <p>{{ title }}</p>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup () {
    const title = ref('')

    const setTitle = val => {
      title.value = val
    }
    return { title, setTitle }
  }
}
</script>

<style lang="scss" scope>
.container {
  width: 100%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  img {
    width: 2.133333rem;
  }
  p {
    color: #fff;
  }
}
</style>

我们就来新建一个js文件,用来写loading的自定义指令逻辑

const loadingDirective = { 
    mounted(el,binding) { 
        
    },
    updated(el,binding) {
        
    },
}

return loadingDirective  // 导出

我们在创建的 loadingDirective 对象中,写入两个钩子函数,因为我们希望在这两个周期,对它进行操作,
那我们怎么创建这个组件对应的 DOM 呢?
我们先新建一个新的vue实例,再去进行一个动态的挂载,挂载之后我们就可以拿到这个 DOM 的实例了,具体怎么做呢我们再继续往下写

import { createApp } from 'vue' //导入createApp方法
import Loading from './loading' // 导入我们写好的 loading 组件

const loadingDirective = {
    mounted(el, binding) {
        // 创建app对象 跟组件为我们写好的 loading 组件
        const app = createApp(Loading)

        //动态创建一个div节点,将app挂载在div上,我们的 loading 组件将替换此 div 标签的 innerHTML
        const instance = app.mount(document.createElement('div'))
    },
    updated(el, binding) {},
}

return loadingDirective  // 导出

接下来我们要到了在 mounted 的时候,loading 是否显示的判断了,在做判断之前,先来看看,我们在写好loading自定义指令之后,该如何使用

<template>
  <div v-loading="loading"></div> // 只需在节点上写上 v-loading='loading' 即可, 后边的loading是一个值
</template>
<script>
import { ref, onMounted } from 'vue'

export default {
  setup () {
    const loading = ref(true)  // 默认让loading 值为true ,传给loading组件

    onMounted(() => {  
      setTimenout(() => {    //模拟数据加载完成之后更改 loading 状态
         loading.value = false    
      }, 5000)
    })
    return { loading}
  }
}
</script>

此时我们在loading 组件中就可接收到传入的值,我们根据值来判断是否显示 loading 组件

const loadingDirective = {
    mounted(el, binding) {
        // 创建app对象 跟组件为我们写好的 loading 组件
        const app = createApp(Loading)

        //动态创建一个div节点,将app挂载在div上
        const instance = app.mount(document.createElement('div'))

        // 因为在updated也需要用到 instance 所以将 instance 添加在el上 ,在updated中通过el.instance 可访问到
        el.instance = instance
        if (binding.value) { // v-loading传过来的值储存在 binding.value 中 
            append(el)
        }
    },
    updated(el, binding) {
        remove(el)
    },
}

function append(el) {
    el.appendChild(el.instance.$el)  //向el节点插入动态创建的 div 节点 , 内容就是我们的 loading 组件
}
function remove(el) {
    el.removeChild(el.instance.$el)  //移除动态创建的 div 节点
}

上边我写了两个方法,一个是插入节点,一个是移除节点,在一开始的时候将节点插入,在v-loading的值发生改变时候出发updated我们将节点移除, 但是这样写是不合适的,我们需要完善一下在updated中的操作,

  updated (el, binding) {
    if (binding.value !== binding.oldValue) { // 如果value的值有改变,那么我们去判断进行操作
      binding.value ? append(el) : remove(el)   // 三元表达式   true 添加,false 移除
    }
  }

因为我们需要在全局使用,那么我们将在 main.js 中去引入注册

import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import '@/assets/scss/index.scss'
import loadingDirective from '@/views/loading/directive' //引入loading自定义指令

createApp(App)
  .use(store)
  .use(router)
  .directive('loading', loadingDirective)  //全局注册loading指令
  .mount('#app')

现在一个简单的指令已经写完了,但是它还有两点需要完善的地方

1.是它位置定位的问题,如果所在挂载的 DOM 元素有定位属性,那么没有问题,但是所在挂载的 DOM 元素没有定位的时候,那么它自身的绝对定位,就有可能出现问题。
2.我们的需求还有一段文字需要进行展示,或许不同的地方需要等待时的文案会不同。

首先来解决第一个小问题 , 在插入的时候去判断挂载的节点有没有定位,如果没有则为其添加相对定位

const className = 'g-relative' // g-relative 是我在全局写的样式名
function append (el) {
  const style = getComputedStyle(el)
  if (['absolute', 'relative', 'fixed'].indexOf(style.position) === -1) {
    el.classList.add(className) // 通过此API可以添加类名
  }
  el.appendChild(el.instance.$el) //向el节点插入动态创建的 div 节点 , 内容就是我们的 loading 组件
}

g-relative 是我在全局scss文件中写的样式

.g-relative {
    position: relative;
}

在解决最后一个小问题,动态显示文案
在使用的地方使用 :[]的语法

<template>
  <div class="container" v-loading:[title]="loading" ref="hello"></div>
</template>
<script>
import { ref, onMounted } from 'vue'

export default {
  setup () {
    const title = ref('请稍后...')
    const loading = ref(true)

    return { loading, title }
  }
}
</script>

传入title之后需要进行接收,并插入title在loading中
通过binding.arg可接收到:[]传来的值

const loadingDirective = {
  mounted (el, binding) {
    const app = createApp(loading)
    const instance = app.mount(document.createElement('div'))
    el.instance = instance
    if (binding.value) {
      append(el)
    }
    if (binding.arg !== 'undefined') {   // 在此判断是否有title值
      instance.setTitle(binding.arg)    // setTitle 使我们在loading组件中定义的方法,可返回第一段代码查看
    }
  },
  updated (el, binding) {
    if (binding.arg !== 'undefined') {  // 在此判断是否有title值
      el.instance.setTitle(binding.arg)  //  // setTitle 使我们在loading组件中定义的方法,可返回第一段代码查看
    }
    if (binding.value !== binding.oldValue) {
      binding.value ? append(el) : remove(el)
    }
  }
}

现在就完全实现了 loading 自定义指令的实现!
哪里有问题还请留言交流哦 886

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容