这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

loading的展示和取消可以说是每个前端对接口的时候都要关心的一个问题。这篇文章将要帮你解决的就是如何结合axios更加简洁的处理loading展示与取消的逻辑

首先在我们平时处理业务的时候loading一般分为三种:按钮loading局部loading,还有全局loading

按钮loading

其实想写这篇博客的诱因也是因为这个按钮loading ,在大多数时候我们写按钮loading业务的时候是这样写的。

const loading = ref(false)
try {
   loading.value = true
   const data = await axios.post(`/api/data`)
}
finally {
   loading.value = false
}

或者这样的写的

const loading = ref(false)
loading.value = true
axios.post(`/api/data`)
  .then(data => {
       //do something
  })
  .finally(() => {
       loading.value = false
  })

可以看到 我们总要处理loading的开始与结束状态。而且好多接口都要这么写。这样太繁琐了,那我们可不可以这样呢?

vue3版本

const loading = ref(false)
const data = await axios.post(`/api/data`,{loading:loading})

把loading的状态给axios统一处理。这样代码是不是就简洁多了呢?处理方式也很简单。

// 请求拦截器
axios.interceptors.request.use(config = >{
   if (config.loading) {
     config.loading.value = true
  }
})
// 响应拦截器
axios.interceptors.response.use(
   response => {
       if (response.config.loading) {
           res.config.loading.value = false
      }
  },
   error => {
       if (error.config.loading) {
           config.loading.value = false
      }
  }
)

我们只需要在axios的拦截器中改变loading的值就可以,注意一定要传入一个ref类的值。这种写法也仅适用于vue3。vue2是不行的。

vue2版本

在vue2里面我们可能会想到这样写。

<template>
   <a-button loading="loading.value">
       保存
   </a-button>
</template>

<script>
 export default {
   data () {
       return {
           loading: { value: false },
      }
  },
   mounted () {
       const data = await axios.post(`/api/data`,{loading:this.loading})
  },
}
</script>
//拦截器和vue3写法一样

但是很遗憾这样是无法生效的。原因如下

//接口调用
axios.post(接口地址,配置项)
//拦截器
axios.interceptors.request.use(配置项 => {})

在axios中我们接口调用传入的配置项 和 拦截器返回的配置项 并不是同一个内存地址。axios做了深拷贝处理。所以传入的loading对象和返回的loading对象并不是同一个对象。所以我们在拦截器中修改是完全没有用的。

可是vue3为什么可以呢?因为ref返回的对象是RefImpl类的实例 并不是一个普通的对象,axios在做深拷贝的时候没有处理这种实例对象。 所以我们就可以从这里出发来改造一下我们的axios写法。代码如下:

axios代码:

const _axios = axios.create({
 method: `post`,
 baseURL: process.env.VUE_APP_BASE_URL,
})
//注意:拦截器中比vue3多了个loading!!!
// 请求拦截器
_axios.interceptors.request.use(config = >{
   if (config.loading) {
     config.loading.loading.value = true
  }
})
// 响应拦截器
_axios.interceptors.response.use(
   response => {
       if (response.config.loading) {
           res.config.loading.loading.value = false
      }
  },
   error => {
       if (error.config.loading) {
           config.loading.loading.value = false
      }
  }
)

export const post = (url, params, config) => {
   if (config?.loading) {
       class Loading {
           loading = config.loading
      }
       config.loading = new Loading()
  }
   return _axios.post(url, params, config)
}

使用方式:

<template>
   <a-button loading="loading.value">
       保存
   </a-button>
</template>

<script>
 import { post } from '@api/axios'
 export default {
   data () {
       return {
           //这里的loading可以取任意名字。但是里面必须有value
           loading: { value: false },
      }
  },
   mounted () {
       const data = await post(`/api/data`,{loading:this.loading})
  },
}
</script>

可以看到实现的原理也很简单。我们在axios里面把出传入的config中的loading对象也变成一个实例对象就好了。在实例对象中记录我们传入的对象,也是以为这里我们会比vue3的写法多一个loading,从而实现响应式。

高阶函数版本

以上的方案看起来还是很不友好。如果我们不拘泥于在拦截器中封装呢?

axios代码如下:

const _axios = axios.create({
 method: `post`,
 baseURL: import.meta.env.VITE_BASE_URL,
})


// 请求拦截器
_axios.interceptors.request.use()
// 响应拦截器
_axios.interceptors.response.use()

async setRequest (callBack, url, params, config) {
   //添加按钮的loading
   if (config.loading) {
     config.loading.value = true
  }
   try {
       return await callBack(url, params, config)
  }
   catch (error) {
       return Promise.reject(error)
  }
   finally {
       //关闭按钮的loading状态
       if (config.loading) {
         config.loading.value = false
      }
  }
}

export const post = (url, params, config) => {
   return setRequest(_axios.post,url, params, config)
}

以上代码仅仅是个代码思路。我们可以使用高阶函数二次封装 axios的post函数。get等函数也是一样的,这里就不一一举例了。

局部loading

局部loading的添加有两种方式:

  1. 使用自定义指令 传入true和false 。这样的缺陷是不够灵活,组件内的元素就很难局部添加了, 只能全组件添加。值得一提的是,改变true和false的逻辑就可以用我们上述的按钮loading方法。具体的实现方式这里就不再讲述了,如果需要的话可以评论区留言。
  2. 在axios中封装。每次调用接口的时候传入需要添加loading的dom。接口调用完毕删除dom。实现方法如下。

这里是vue3 + antdV3 技术栈的一个封装。这里用hooks把设置删除loading的逻辑给拆了出去。

axios代码:

const _axios = axios.create({
 method: `post`,
 baseURL: import.meta.env.VITE_BASE_URL,
})

const { setLoading, deleteLoading } = useAxiosConfig()
// 请求拦截器
_axios.interceptors.request.use(config = >{
   setLoading(config)
})
// 响应拦截器
_axios.interceptors.response.use(
   response => {
       deleteLoading(res.config)
  },
   error => {
       deleteLoading(res.config)
  }
)

//这里也可以 不拘泥于拦截器。使用上述高阶函数的方式。
export const post = (url, params, config) => {
   return _axios.post(url, params, config)
}

hooks代码

import { createApp } from 'vue'
import QSpin from '@/components/qSpin/QSpin.vue'
import type { RequestConfig, AxiosError } from '@/types/services/http'
export default function () {
 /** 使用WeakMap类型的数据 键名所指向的对象可以被垃圾回收 避免dom对象的键名内存泄漏 */
 const loadingDom = new WeakMap()
 /**
  * 添加局部loading
  * @param config
  */
 const setLoading = (config: RequestConfig) => {
   const loadingTarget = config.dom
   if (loadingTarget === undefined) return
   const loadingDomInfo = loadingDom.get(loadingTarget)
   if (loadingDomInfo) {
     loadingDomInfo.count++
  } else {
     const appExample = createApp(QSpin)
     const loadingExample = appExample.mount(document.createElement(`div`)) as                 InstanceType<typeof QSpin>
     loadingTarget.appendChild(loadingExample.$el)
     loadingExample.show(loadingTarget)
     loadingDom.set(loadingTarget, {
       count: 1, //记录当前dom的loading次数
       appExample,
    })
  }
}
 /**
  * 删除局部loading
  * @param config
  */
 const deleteLoading = (config: RequestConfig) => {
   const loadingTarget = config.dom
   if (loadingTarget === undefined) return
   const loadingDomInfo = loadingDom.get(loadingTarget)
   if (loadingDomInfo) {
     if (--loadingDomInfo.count === 0) {
       loadingDom.delete(loadingTarget)
       loadingDomInfo.appExample.unmount()
    }
  }
}  return { setLoading, deleteLoading }
}

基础逻辑,很简单。只需要接口请求的时候的添加loading ,接口响应完成的时候删除loading。但是随之而来的就有一个问题,如果多个接口同时请求 或者 一个接口频繁请求需要覆盖的都是同一个dom,这样我们添加的loading就会有很多个相同的,相互覆盖。因此上述代码定义了一个loadingDom 记录当前正在loading的dom有哪些,如果有一样的进来的 就把count加一 ,结束后就把count减一。如果count为零则删除loading。

使用实例代码:

<template>
 <div>
   <div ref="head_dom">我是头部数据</div>
   <a-card ref="card_dom">我是卡片内容</a-card>
 </div>
</template>

<script setup lang="ts">
 import { post } from '@api/axios'
 import { ref, onMounted } from 'vue'
 const head_dom = ref()
 const card_dom = ref()
 //这边写了两个是为了演示下 直接在html标签上面绑定ref拿到的就是dom。在组件上面拿到的是组件实例要$el一下
 onMounted(async () => {
   const data1 = await post(`/api/head`, { dom: head_dom.value })
   const data2 = await post(`/api/card`, { dom: card_dom.value.$el })
})
</script>

下面简单解释下hooks代码中QSpin组件的代码。

<template>
 <div v-show="visible" class="q-spin">
   <spin tip="加载中" />
 </div>
</template>

<script setup lang="ts">
 import { Spin } from 'ant-design-vue'
 import { ref } from 'vue'

 const visible = ref(false)
 const show = (dom: HTMLElement) => {
   visible.value = true
   dom.style.transform = dom.style.transform || `translate(0)`
}
 defineExpose({ show })
</script>

<style scoped lang="less">
.q-spin {
   position: fixed;
   z-index: 999;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   display: flex;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   background-color: rgb(0 0 0 / 10%);
}
</style>

这里是对antdv3的Spin组件做了一个简单的二次封装。主要讲解的就是一个loading覆盖传入dom的方法。

大多数地方使用的方式都是 relative 和 absolute 定位组合的方式,但是这里采用了transform 和 fixed定位组合的方式。因为我们的项目中可能出现这样一种情况

  <div style="position: relative">
   <div ref="div_dom">
     <div style="position: absolute">我是内容</div>
   </div>
 </div>

假如 我们要给中间的的div添加loading, 使用relative 和 absolute 定位组合的方式。那么中间的div就会在样式表种添加一个position: relative的属性,这样代码就会变成这样

 <div style="position: relative">
   <div style="position: relative" ref="div_dom">
     <div style="position: absolute">我是内容</div>
   </div>
 </div>

很明显 我们第三层div定位的根节点就从第一层变成了第二层,这样就会有可能导致我们样式的错乱。因此笔者采用了transform 和 fixed定位组合的方式。虽然上述的情况可能还会出现 但是会大大减少出现的可能性。

全局loading

这个就很简单了。如果你封装好了局部的loading 直接在配置项的dom中传入document.body即可!

本文转载于:

https://juejin.cn/post/7215424335719923772

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录--axios和loading不得不说的故事的更多相关文章

  1. 与《YII框架》不得不说的故事—5篇目录

    与<YII框架>不得不说的故事—基础篇 第1章 课程目标 1-1 课程目标 (00:54) 第2章 课前知识准备 2-1 YII的启动和安装 (05:12) 2-2 YII请求处理流程 ( ...

  2. 【征文】Hadoop十周年特别策划——我与Hadoop不得不说的故事

    2016年是Hadoop的十周年生日,在今年,CSDN将以技术和实战为主题与大家共同为Hadoop庆生.其主要内容包含Hadoop专业词典.系列视频技术解析.Hadoop行业实践.线上问答.线下沙龙. ...

  3. asList和ArrayList不得不说的故事

    目录 简介 创建ArrayList UnsupportedOperationException asList 转换 总结 asList和ArrayList不得不说的故事 简介 提到集合类,ArrayL ...

  4. 我与PHP,ULM和Vue.js不得不说的故事(一个放荡不羁与一个神神秘秘一个似曾相识,从入门到放弃记录第二章)

    ·关于UML(git) 究竟是命运在茫茫语言之中遇到了你,还是我的魅力让你向我奔涌而来.好吧都不是,我俩就像古代包办婚姻,被专业牢牢的绑在一起了,既然都是一条绳上的蚂蚱.我我们应该能体谅彼此的不容易, ...

  5. 我与PHP和git不得不说的故事(梦开始的地方,从入门到放弃记录第一章)

    ·关于下载 阿瑶瑶跟wampsever官网搏斗了一下午,其实我觉得教材可能在PUA我.谷歌说它给的网址安全证书过期,然后下载以断网收场.(阿瑶的第一战,以失败告终) [经过我玲姐指点,下载路径变为迅雷 ...

  6. Visual Studio 20周年,我和VS不得不说的故事(内含福利)

    Visual Studio 2017正式版已如期发布(点击这里查看发布全记录)!自去年 11 月正式宣布 Visual Studio 项目之后,微软终于正式推出了“宇宙最强集成开发环境(IDE)”的最 ...

  7. Visual Studio 20周年,我和VS不得不说的故事

    Visual Studio 2017正式版已如期发布(点击这里查看发布全记录)!自去年 11 月正式宣布 Visual Studio 项目之后,微软终于正式推出了“宇宙最强集成开发环境(IDE)”的最 ...

  8. 和java面试不得不说的故事

    一直都没有想到,可以有机会可以面试他人,很感谢现在的公司,给我不少的机会可以尝试从不同方面尝试一些工作,在入职现公司之前也作为面试者参加过不少面试,不过还好,面试通过率都还好,大部分是待遇谈不拢.现在 ...

  9. 记录axios在IOS上不能发送的问题

    最近 遇到 了axios在IOS上无法发送的问题,测试 了两个 苹果 机,IOS10上不能发送,IOS12可以,百度了下,找到了解决方法.记录下吧 首先引入qs,这个安装axios也已经有了吧:然后在 ...

  10. 微信、QQ和手机号之间不得不说的故事!

    发文字,发图片,发心情,视频聊天,查看附近的人,微信能干的事情QQ都可以,那么它们有什么区别,我QQ用得好好的为什么要我联系人都导到微信去?我们很早就有了QQ,但是在QQ时代,我们虽然用QQ发消息聊天 ...

随机推荐

  1. 【Android】使用MediaExtractor、MediaMuxer去掉视频文件中的音频数据

    1 简介 ​ 本文以 mp4 文件为例,讲解去音频操作.mp4 是一种视频封装的容器,里面包含音频(audio)和视频(video)数据,对应的数据编码格式分别为 aac 和 h264.在去音频过程中 ...

  2. spring boot读取json文件并实现接口查询

    0.源码下载 https://download.csdn.net/download/IndexMan/84238085 1.说明 最近需要在spring boot项目启动时读取json文件并保存到Li ...

  3. dp题单——区间dp

    一.基本概念 1.链式区间dp for(int len = 2; len <= n; len++){ //枚举区间长度 for(int i = 1; i + len - 1 <= n; i ...

  4. Golang从入门到跑路-从基础到微服务学习路线图

    收录的awesome-go项目,学习基础系列,go项目实战,go源码分析,go开发者成长路线图等等,把他们收集起来学习. 地址:https://github.com/jiujuan/go-collec ...

  5. 【Android 逆向】【攻防世界】ill-intentions

    1. apk 安装到手机, 啥输入框都没有 2. apk拖入到jadx中看看 public class MainActivity extends Activity { @Override // and ...

  6. android 逆向笔记

    壳检测工具 GDA 2. 逆向分析APP 一般流程 1. 使用自动化检测工具检测APP是否加壳,或者借助一些反编译工具依靠经验判断是否加壳 2. 如果apk加壳,则需要先对apk进行脱壳 3. 使用` ...

  7. 如何保证消息顺序执行(Rabbitmq/kafka)

    转载: https://www.cnblogs.com/-wenli/p/13047059.html https://www.jianshu.com/p/02fdcb9e8784

  8. 正则计算器---day19

    计算下面表达式最后的结果 strvar = "1-2*((60-30+(-40/5)*(9-2*5/3+7/3*99/4*2998+10*568/14))-(-4*3)/(16-3*2))& ...

  9. Java新建一个子线程异步运行方法

    如何在运行主方法的同时异步运行另一个方法,我是用来更新缓存: 1. 工具类 public class ThreadPoolUtils { private static final Logger LOG ...

  10. 使用ConfuserEx代码混淆工具保护你的.NET应用程序

    前言 .NET应用如何防止被反编译?这个对于我们.NET开发而言是一个值得关注和重视的问题,防止应用程序被反编译的手段有很多本文我们主要讲讲如何使用ConfuserEx .NET开源免费的代码混淆工具 ...