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

2023-10-26

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

所以我们可以使用 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)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

封装v-loading全局自定义指令 的相关文章

  • 当鼠标移动缓慢时,Bootstrap 4中的菜单消失

    我在跑这把小提琴 https jsfiddle net Chamster o23865p6 当鼠标指针从 开始 快速移动到展开的菜单时 一切都很好 然而 当移动速度较慢时 菜单会关闭 因为感觉就像鼠标左移 const menu li dro
  • 如何解释这个正则表达式 /[\W_]/g

    我的代码是 var result2 result replace W g replace replace 该代码有效 我得到了我需要完成的工作 但我不明白正则表达式如何 W g有效 但我找不到任何我理解的文档 g这是一个全局正则表达式 因此
  • Ace Editor 获取当前选定的行号和文本

    我目前正在使用 Ace Editor 但我在文档中找不到与检索当前所选行号及其文本相关的任何内容 有任何想法吗 首先 定义 选定行 ace 中的选择可以跨多行设置 如果您的意思是 未设置选择 当前行是光标闪烁的行 var currline
  • YouTube iframe 不响应 postMessage 命令

    我正在尝试使用来自父级的 postMessage 命令来控制 YouTube iframe 但它似乎不起作用 由于多种原因 我没有使用 YouTube API 只是使用带有 YouTube 嵌入视频的普通 iframe 我尝试发送命令的方式
  • 使用 Asynchronous ReadableStream 和 Response 从 Service Worker 的 fetch 事件返回 HTML

    这个问题类似于我的另一个问题 https stackoverflow com questions 62457644 use readablestream with response to return html from fetch eve
  • 使用 BeautifulSoup 在 python 中抓取多个页面

    我已经设法编写代码来从第一页中抓取数据 现在我不得不在这段代码中编写一个循环来抓取接下来的 n 页 下面是代码 如果有人可以指导 帮助我编写从剩余页面中抓取数据的代码 我将不胜感激 Thanks from bs4 import Beauti
  • Vue - 如何附加组件?

    我正在尝试创建一个按钮 一旦按下该按钮 就会向我的组件添加一个子组件 我的代码可以工作 只是 HTML 被解析为字符串 这是父组件
  • 滚动内容上的 CSS 框阴影

    我想要一个带有插入框阴影的 div 其中包含滚动的内容 不幸的是 盒阴影不会投射在内容中的元素上 而是投射在背景上 但我希望它也覆盖内容元素 我偶然发现了这个解决方案 http jsfiddle net HPkd3 http jsfiddl
  • 在 Javascript 中获取类的所有实例

    我以为这个问题已经有了答案 但我似乎找不到答案 如何在 Javascript 中的此类的所有实例上运行特定的类方法 这必须在我不知道实例名称的情况下完成 我想我可以在类中使用某种静态变量来存储所有实例 但这在 JS 中似乎不存在 那么如何在
  • 什么是window.onpaint?

    有人推荐here https stackoverflow com questions 6944037 how to run java script code before page load that window onpaint用于在页面
  • Javascript 子字符串方法帮助

    长话短说 我正在开发一个 Web 应用程序并在其中使用 AJAX 我试图禁用点击时链接的默认操作 将哈希值附加到链接 然后从网址中删除 我遇到的问题是 虽然哈希值被相应地附加 但子字符串方法并没有提取 而是提取了它后面的字母 这是我的代码
  • 使用 arrayRemove() 从 firestore 9 数组中删除对象?

    我正在尝试从 firestore 中的数组中删除一个对象 但遇到了障碍 执行删除的要求或参考是什么 对象中的一个键值是否足以执行删除操作 或者该对象是否应该与要删除的对象相同 const deleteWeek async gt const
  • ASP.Net Web 应用程序 Jquery Photoviewer 和 Ajaxical 更新

    有一个错误我的网站 http new mnarfezhom com 请进入右数第三个菜单 有些图像只能通过 jquery photoviewer 显示 onlclick 这很好用 现在 当我单击页面底部的 Ajaxical 更新按钮时 问题
  • Knockout JS 与 Ratchet 和 Push.js 配合得很好,直到我添加数据转换

    我正在使用 Ratchet js push js 库为移动 Web 应用程序创建 UI 在这个库中 链接是通过将要加载的文件 推送 到 content DOM 元素中而不是加载整个页面来处理的 但是 push js 在加载页面时不会加载它找
  • Spring MVC - 两次提供内容

    我已经花了一周时间寻找有关如何将内容服务器到我的网页的指导 两次 因为使用 Model 或 ModelAndView 切断内容一次可以工作 但如果用户再次与页面交互 我希望它加载更多内容同一页 Java Spring 后端方法 Get 有效
  • 如何使用 Chart.js 在堆积条形图中显示内联值?

    我正在使用 Chart js 库在堆叠条形图中显示一些值 但我正在努力找出如何显示条形图中的值 即 现在 我有以下代码 可以在条形顶部显示数字 但我想知道如何在条形内部显示它们 var numberWithCommas function x
  • 使用两个键执行自动完成 - Material UI with React

    当使用两个值之一搜索时 我试图自动完成输入 title and year 但是 它仅在我搜索时才有效title 当我搜索时year 它不显示任何选项 示例代码 export default function ComboBox return
  • 如何在 Mongoose 中设置文档创建的 TTL 日期?

    我正在尝试做一个promoCodeMongoose 中的架构 创建时 我需要能够设置促销代码的到期日期 促销代码不一定相同TTL 我在看这个问题 https stackoverflow com questions 14597241 sett
  • Webpack 和外部库

    我正在尝试 webpack http webpack github io http webpack github io 看起来真的很不错 但我有点被困在这里了 假设我正在为库 f ex jQuery 使用 CDN 然后在我的代码中 我想要r
  • IFrame Resizer 未调整大小

    我正在这个页面上工作 http factor1hosting com dnaz wordpress certifications http factor1hosting com dnaz wordpress certifications 我

随机推荐