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


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

    所以我们可以使用 v-loading 去优化一下, 增加用户的体验性


    我们可以有两种方式去实现:

    1. 定义一个 loading 的组件, 然后在每一个需要显示 loading 效果的组件中手动的导入

    2. 使用自定义指令(v-loading)去动态的添加 loading 效果

    显然第二种方案是要比第一种方案优雅的

    如果给每一个需要添加 loading 效果的组件都去手动导入注册的话, 这个行为未免有点繁琐

    首先我们需要明确, 我们需要在一个 DOM 元素中插入一个 loading 的效果

    且这个 loading 效果并不是一尘不变的, 在当组件数据更新的之后; 我们需要将 loading 效果进行移除


    第一步: 

    创建 loading.vue 公共的基础组件

    此组件需要向外暴露一个 setTitle 方法, 用于动态修改 loading 的文案

    1. <template>
    2. <div class="loading">
    3. <div class="loading-content">
    4. <img width="24" height="24" src="./loading.gif">
    5. <p class="desc">{{title}}p>
    6. div>
    7. div>
    8. template>
    9. <script>
    10. export default {
    11. name: 'loadingCom',
    12. data () {
    13. return {
    14. title: ''
    15. }
    16. },
    17. methods: {
    18. setTitle (title) {
    19. this.title = title
    20. }
    21. }
    22. }
    23. script>
    24. <style lang="scss" scoped>
    25. .loading {
    26. position: absolute;
    27. top: 50%;
    28. left: 50%;
    29. transform: translate3d(-50%, -50%, 0);
    30. .loading-content {
    31. text-align: center;
    32. .desc {
    33. line-height: 20px;
    34. font-size: $font-size-small;
    35. color: $color-text-l;
    36. }
    37. }
    38. }
    39. style>

    第二步:

    vue 提供了一个 api (directive), 用于注册自定义指令

    此 api 函数中需要传入两个参数:

    1. 自定义指令名称

    2. 配置项

    所以, 我们还需要创建 v-loading 的配置文件(directive)

    然后在 main.js 入口文件中进行注册

    1. // 导入v-loading的配置项
    2. import loadingDirective from '@/components/base/loading/directive'
    3. createApp(App).use(store).use(router).directive('loading', loadingDirective).mount('#app')

    第三步:

    现在我们就需要去封装 loadingDirective 这一个配置项 

    1. 首先导入 loading 组件

    2. 导入 createApp 函数

    3. 定义 loadingDirective 对象, 此对象中有两个成员 mounted(组件挂载时) 和 updated(组件更新时)

    4. 先完成 mounted 函数的逻辑

    1. import loading from './loading.vue'
    2. import { createApp } from 'vue'
    3. const loadingDirective = {
    4. mounted (el, binding) {
    5. // 创建根组件
    6. const app = createApp(loading)
    7. // 获取vue实例
    8. const instance = app.mount(document.createElement('div'))
    9. // 因为在添加方法内部需要使用到instance, 但是instance是一个局部变量
    10. // 那么就添加到el对象中
    11. el.instance = instance
    12. if (binding.value) {
    13. append(el)
    14. }
    15. }
    16. }
    17. // 添加元素方法
    18. function append (el) {
    19. el.appendChild(el.instance.$el)
    20. }

    5. 组件数据更新的时候, 需要执行 updated 钩子函数

    1. import loading from './loading.vue'
    2. import { createApp } from 'vue'
    3. const loadingDirective = {
    4. mounted (el, binding) {
    5. const app = createApp(loading)
    6. const instance = app.mount(document.createElement('div'))
    7. el.instance = instance
    8. if (binding.value) {
    9. append(el)
    10. }
    11. },
    12. updated (el, binding) {
    13. // 如果旧值不等于新值的话, 就需要动态的执行添加和移除元素方法
    14. if (binding.oldValue !== binding.value) {
    15. // 组件没有再次初始化的前提下, 何时会再次显示loading效果
    16. // 比如, 分页组件, 当当前页码值发生变化的时候需要重新请求数据; 渲染数据
    17. binding.value ? append(el) : remove(el)
    18. }
    19. }
    20. }
    21. function append (el) {
    22. el.appendChild(el.instance.$el)
    23. }
    24. // 移除元素方法
    25. function remove (el) {
    26. el.removeChild(el.instance.$el)
    27. }
    28. export default loadingDirective

    配置文件写到这里, 一个 v-loading 自定义指令就完成了

    但是我们还可以做一些优化:

    1. 不要依赖父元素的定位, 因为 loading 效果需要在父元素的正中间显示

    2. 可以动态的设置 loading 效果的文案


    先来完成第一个优化

    我们将对一些 dom 的操作提取到一个 dom.js 文件中

    1. export function addClass (el, className) {
    2. // 不要对一个元素重复添加class类
    3. if (!el.classList.contains(className)) {
    4. el.classList.add(className)
    5. }
    6. }
    7. export function removeClass (el, className) {
    8. // 如果删除一个不存在的class类是不会报错的
    9. el.classList.remove(className)
    10. }

    然后我们需要对 loadingDirective 配置项进行修改

    1. import loading from './loading.vue'
    2. import { createApp } from 'vue'
    3. import { addClass, removeClass } from '@/assets/js/dom'
    4. // 在css中定义一个class类
    5. const relativeCls = 'g-relative'
    6. const loadingDirective = {
    7. mounted (el, binding) {
    8. const app = createApp(loading)
    9. const instance = app.mount(document.createElement('div'))
    10. el.instance = instance
    11. if (binding.value) {
    12. append(el)
    13. }
    14. },
    15. updated (el, binding) {
    16. if (binding.oldValue !== binding.value) {
    17. binding.value ? append(el) : remove(el)
    18. }
    19. }
    20. }
    21. function append (el) {
    22. // 动态的获取当前el元素的style
    23. const style = getComputedStyle(el)
    24. // 判断是否存在以下几种定位
    25. if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
    26. addClass(el, relativeCls)
    27. }
    28. el.appendChild(el.instance.$el)
    29. }
    30. function remove (el) {
    31. // 组件数据更新的时候需要移除掉class类
    32. removeClass(el, relativeCls)
    33. el.removeChild(el.instance.$el)
    34. }
    35. export default loadingDirective

    第二个优化:

    在组件挂载的时候, 去动态的给 loading 效果添加文案

    这里需要使用到 loading 组件向外暴露的 setTitle 函数

    然后通过 binding.arg 方法获取传入的数据, 再调用 setTitle 进行动态的设置

    1. import loading from './loading.vue'
    2. import { createApp } from 'vue'
    3. import { addClass, removeClass } from '@/assets/js/dom'
    4. const relativeCls = 'g-relative'
    5. const loadingDirective = {
    6. mounted (el, binding) {
    7. const app = createApp(loading)
    8. const instance = app.mount(document.createElement('div'))
    9. el.instance = instance
    10. // 获取组件传入的文案数据
    11. const title = binding.arg
    12. if (typeof title !== 'undefined') {
    13. instance.setTitle(title)
    14. }
    15. if (binding.value) {
    16. append(el)
    17. }
    18. },
    19. updated (el, binding) {
    20. // 组件更新的时候也是一样
    21. const title = binding.arg
    22. if (typeof title !== 'undefined') {
    23. el.instance.setTitle(title)
    24. }
    25. if (binding.oldValue !== binding.value) {
    26. binding.value ? append(el) : remove(el)
    27. }
    28. }
    29. }
    30. function append (el) {
    31. const style = getComputedStyle(el)
    32. if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
    33. addClass(el, relativeCls)
    34. }
    35. el.appendChild(el.instance.$el)
    36. }
    37. function remove (el) {
    38. removeClass(el, relativeCls)
    39. el.removeChild(el.instance.$el)
    40. }
    41. export default loadingDirective

    然后看一下在需要 v-loading 的组件使用

    1. <template>
    2. <div v-loading:[loadingText]="loading">div>
    3. template>
    4. <script>
    5. export default {
    6. components: { Slider, Scroll },
    7. data () {
    8. loadingText: '正在载入...'
    9. }
    10. },
    11. computed: {
    12. loading () {
    13. return // 动态判断loading效果是否显示
    14. }
    15. }
    16. }
    17. script>

    我们还可以将 loadingDirective 这一个配置项兼容不同的自定义指令, 当然前提就是共用这一个配置项的自定义指令的业务应该是差不多

    首先我们得封装一个函数, 这一个函数的返回值是一个对象(也就是配置项)

    然后这一个函数需要接收不同的 vue 组件, 来在 el 上生成不同的实例

    所以我们需要在 update , append 和 remove 方法执行的时候, 区分 el 上不同的实例

    1. import { createApp } from 'vue'
    2. import { addClass, removeClass } from '@/assets/js/dom'
    3. const relativeCls = 'g-relative'
    4. export default function createLoadingLikeDirective (comp) {
    5. return {
    6. mounted (el, binding) {
    7. const app = createApp(comp)
    8. const instance = app.mount(document.createElement('div'))
    9. // 区分不同的组件实例
    10. const name = comp.name
    11. if (!el[name]) {
    12. el[name] = {}
    13. }
    14. el[name].instance = instance
    15. const title = binding.arg
    16. if (typeof title !== 'undefined') {
    17. instance.setTitle(title)
    18. }
    19. if (binding.value) {
    20. append(el)
    21. }
    22. },
    23. updated (el, binding) {
    24. const name = comp.name
    25. const title = binding.arg
    26. if (typeof title !== 'undefined') {
    27. el[name].instance.setTitle(title)
    28. }
    29. if (binding.value !== binding.oldValue) {
    30. binding.value ? append(el) : remove(el)
    31. }
    32. }
    33. }
    34. function append (el) {
    35. const name = comp.name
    36. const style = getComputedStyle(el)
    37. if (['fixed', 'absolute', 'relative'].indexOf(style.position) === -1) {
    38. addClass(el, relativeCls)
    39. }
    40. el.appendChild(el[name].instance.$el)
    41. }
    42. function remove (el) {
    43. const name = comp.name
    44. removeClass(el, relativeCls)
    45. el.removeChild(el[name].instance.$el)
    46. }
    47. }

    那么 loading/directive.js 中的代码需要做修改

    1. import loading from './loading.vue'
    2. import createLoadingLikeDirective from '@/assets/js/create-loading-like-directive'
    3. export default createLoadingLikeDirective(loading)
  • 相关阅读:
    嵌入式基础-电路
    un38.3认证办理所需资料有哪些,UN38.3报告多少钱?
    C#西门子S7 协议通过偏移量的方式读写PLC DB块
    数据分析入门指南:从基础概念到实际应用(一)
    JWT登录认证(3拦截器)
    35岁的腾讯测试员退休?答:存够2千万回家买房
    库函数和头文件
    从effect理解Vue3的响应式原理
    最新版本EasyRecovery15个人免费版电脑数据恢复工具
    一篇文章让你弄懂分布式一致性协议Paxos
  • 原文地址:https://blog.csdn.net/weixin_48524561/article/details/127752944