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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容