经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » JS/JS库/框架 » TypeScript » 查看文章
Vite4+Typescript+Vue3+Pinia 从零搭建(7) - request封装
来源:cnblogs  作者:唯之为之  时间:2023/12/21 9:02:45  对本文有异议

项目代码同步至码云 weiz-vue3-template
基于 axios 封装请求,支持多域名请求地址

安装

  1. npm i axios

封装

utils 目录下新建 request 文件夹,并新建 index.tsrequest.tsstatus.ts 文件。

1. status.ts 文件主要是封装状态码

  1. export const ErrMessage = (status: number | string): string => {
  2. let message: string = ''
  3. switch (status) {
  4. case 400:
  5. message = '请求错误!请您稍后重试'
  6. break
  7. case 401:
  8. message = '未授权!请您重新登录'
  9. break
  10. case 403:
  11. message = '当前账号无访问权限!'
  12. break
  13. case 404:
  14. message = '访问的资源不存在!请您稍后重试'
  15. break
  16. case 405:
  17. message = '请求方式错误!请您稍后重试'
  18. break
  19. case 408:
  20. message = '请求超时!请您稍后重试'
  21. break
  22. case 500:
  23. message = '服务异常!请您稍后重试'
  24. break
  25. case 501:
  26. message = '不支持此请求!请您稍后重试'
  27. break
  28. case 502:
  29. message = '网关错误!请您稍后重试'
  30. break
  31. case 503:
  32. message = '服务不可用!请您稍后重试'
  33. break
  34. case 504:
  35. message = '网关超时!请您稍后重试'
  36. break
  37. default:
  38. message = '请求失败!请您稍后重试'
  39. }
  40. return message
  41. }

此时,eslint会报 switch 前面的空格错误,需要修改 .eslintrc.cjs 里的 indent,修改后,错误消失。

  1. rules: {
  2. // Switch语句 https://zh-hans.eslint.org/docs/latest/rules/indent#switchcase
  3. indent: ['error', 2, { SwitchCase: 1 }]
  4. }

2. request.ts 主要是封装 axios

  1. /**
  2. * 封装axios
  3. * axios 实例的类型为 AxiosInstance,请求需要传入的参数类型为 AxiosRequestConfig,响应的数据类型为 AxiosResponse,InternalAxiosRequestConfig 继承于 AxiosRequestConfig
  4. */
  5. import axios, { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios'
  6. import { ErrMessage } from './status'
  7. // 自定义请求返回数据的类型
  8. interface Data<T> {
  9. data: T
  10. code: string
  11. success: boolean
  12. }
  13. // 扩展 InternalAxiosRequestConfig,让每个请求都可以控制是否要loading
  14. interface RequestInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
  15. showLoading?: boolean
  16. }
  17. // 拦截器
  18. interface InterceptorHooks {
  19. requestInterceptor?: (config: RequestInternalAxiosRequestConfig) => RequestInternalAxiosRequestConfig
  20. requestInterceptorCatch?: (error: any) => any
  21. responseInterceptor?: (response: AxiosResponse) => AxiosResponse
  22. responseInterceptorCatch?: (error: any) => any
  23. }
  24. // 扩展 AxiosRequestConfig,showLoading 给实例默认增加loading,interceptorHooks 拦截
  25. interface RequestConfig extends AxiosRequestConfig {
  26. showLoading?: boolean
  27. interceptorHooks?: InterceptorHooks
  28. }
  29. class Request {
  30. config: RequestConfig
  31. instance: AxiosInstance
  32. loading?: boolean // 用loading指代加载动画状态
  33. constructor(options: RequestConfig) {
  34. this.config = options
  35. this.instance = axios.create(options)
  36. this.setupInterceptor()
  37. }
  38. // 类型参数的作用,T决定AxiosResponse实例中data的类型
  39. request<T = any>(config: RequestConfig): Promise<T> {
  40. return new Promise((resolve, reject) => {
  41. this.instance
  42. .request<any, Data<T>>(config)
  43. .then((res) => {
  44. resolve(res.data)
  45. })
  46. .catch((err) => {
  47. reject(err)
  48. })
  49. })
  50. }
  51. // 封装常用方法
  52. get<T = any>(url: string, params?: object, _object = {}): Promise<T> {
  53. return this.request({ url, params, ..._object, method: 'GET' })
  54. }
  55. post<T = any>(url: string, params?: object, _object = {}): Promise<T> {
  56. return this.request({ url, params, ..._object, method: 'POST' })
  57. }
  58. delete<T = any>(url: string, params?: object, _object = {}): Promise<T> {
  59. return this.request({ url, params, ..._object, method: 'DELETE' })
  60. }
  61. patch<T = any>(url: string, params?: object, _object = {}): Promise<T> {
  62. return this.request({ url, params, ..._object, method: 'PATCH' })
  63. }
  64. put<T = any>(url: string, params?: object, _object = {}): Promise<T> {
  65. return this.request({ url, params, ..._object, method: 'PUT' })
  66. }
  67. // 自定义拦截器 https://axios-http.com/zh/docs/interceptors
  68. setupInterceptor(): void {
  69. /**
  70. * 通用拦截
  71. */
  72. this.instance.interceptors.request.use((config: RequestInternalAxiosRequestConfig) => {
  73. if (config.showLoading) {
  74. // 加载loading动画
  75. this.loading = true
  76. }
  77. return config
  78. })
  79. // 响应后关闭loading
  80. this.instance.interceptors.response.use(
  81. (res) => {
  82. if (this.loading) this.loading = false
  83. return res
  84. },
  85. (err) => {
  86. const { response, message } = err
  87. if (this.loading) this.loading = false
  88. // 根据不同状态码,返回不同信息
  89. const messageStr = response ? ErrMessage(response.status) : message || '请求失败,请重试'
  90. window.alert(messageStr)
  91. return Promise.reject(err)
  92. }
  93. )
  94. /**
  95. * 使用通用实例里的拦截,两个拦截都会生效,返回值以后一个执行的为准
  96. */
  97. // 请求拦截
  98. this.instance.interceptors.request.use(
  99. this.config?.interceptorHooks?.requestInterceptor,
  100. this.config?.interceptorHooks?.requestInterceptorCatch
  101. )
  102. // 响应拦截
  103. this.instance.interceptors.response.use(
  104. this.config?.interceptorHooks?.responseInterceptor,
  105. this.config?.interceptorHooks?.responseInterceptorCatch
  106. )
  107. }
  108. }
  109. export default Request

3. index.ts 主要是创建 Request 实例

  1. /**
  2. * 创建实例,可以多个,当你需要请求多个不同域名的接口时
  3. */
  4. import Request from './request'
  5. import { getToken } from '@/utils/auth'
  6. const defRequest = new Request({
  7. // 这里用 Easy Mock 模拟了真实接口
  8. baseURL: 'https://mock.mengxuegu.com/mock/65421527a6dde808a695e96d/official/',
  9. timeout: 5000,
  10. showLoading: true,
  11. interceptorHooks: {
  12. requestInterceptor: (config) => {
  13. const token = getToken()
  14. if (token) {
  15. config.headers.Authorization = token
  16. }
  17. return config
  18. },
  19. requestInterceptorCatch: (err) => {
  20. return err
  21. },
  22. responseInterceptor: (res) => {
  23. return res.data
  24. },
  25. responseInterceptorCatch: (err) => {
  26. return Promise.reject(err)
  27. }
  28. }
  29. })
  30. // 创建其他示例,然后导出
  31. // const otherRequest = new Request({...})
  32. export { defRequest }

使用

src 目录下新建 api 文件夹,并新建 login.ts

1. login.ts

  1. import { defRequest } from '../utils/request'
  2. export const loginApi = (params: any) => {
  3. // 设置 showLoading,timeout 会覆盖index.ts里的默认值
  4. return defRequest.post<any>('/login', params, { showLoading: false, timeout: 1000 })
  5. }

2. 修改 login.vue

  1. <script setup lang="ts">
  2. import { ref } from 'vue'
  3. import { storeToRefs } from 'pinia'
  4. import { useUserStore } from '@store/user'
  5. import { loginApi } from '@/api/login'
  6. defineOptions({
  7. name: 'V-login'
  8. })
  9. const userStore = useUserStore()
  10. const { userInfo, token } = storeToRefs(userStore)
  11. let userName = ref(userInfo.value.name)
  12. let userToken = ref(token)
  13. const updateUserName = () => {
  14. userStore.setUserInfo({
  15. name: userName.value
  16. })
  17. }
  18. const updateUserToken = () => {
  19. userStore.setToken(userToken.value)
  20. }
  21. const login = () => {
  22. loginApi({
  23. name: userName.value
  24. })
  25. .then((res) => {
  26. userName.value = res.name
  27. userToken.value = res.token
  28. updateUserToken()
  29. })
  30. .catch((err) => {
  31. console.log(err)
  32. })
  33. }
  34. </script>
  35. <template>
  36. <div>login page</div>
  37. name:
  38. <input type="text" v-model="userName" @input="updateUserName" />
  39. <br />
  40. token:
  41. <input type="text" v-model="userToken" />
  42. <hr />
  43. <button @click="login">login</button>
  44. </template>
  45. <style scoped></style>

点击 login 按钮,即可看到请求。
image

说明

对于 axios 的封装和使用,这里要说明几点:

1. 为什么要使用 InternalAxiosRequestConfig

axios 源码有修改,拦截器传入和返回的参数不再是 AxiosRequestConfig,而是这个新类型 InternalAxiosRequestConfig
想要具体了解,可以查看这篇博文 https://blog.csdn.net/huangfengnt/article/details/131490913

2. Request 里的 config 参数

constructor 里的 this.config 会接受所有实例参数,所以通用实例拦截里使用的是 this.config?.xxx
通用拦截里使用的是 config.showLoading,而不是 this.config.showLoading,是为了我们在实际的 api/login.ts 里可以再传入 showLoading,以满足我们单个请求的要求。而通过 this.config 里获取的配置是 request/index.ts 里传入的配置。在 config.showLoading 之前我们可以打印下这两个 configconsole.log(this.config, config) 结果如下:
image

如果在 login.ts 里不传入 showLoading,那么 config.showLoading 会去拿通用实例 request/index.ts 里的 showLoading
** 当然如果不需要全局加载动画,整个 loading 也都可以去掉 **

3. 总结下 request/index.tsapi/login.ts 里的参数有什么不同

request/index.ts 里可以建多个实例,一般以 baseURL 来判断是否要多个,它的参数是当前url下的通用参数,拦截规则也是;
api/login.ts 是具体的请求,它的大部分参数是url和请求传参。同一个 baseURL 下有的请求有特殊的要求,那你就可以去加一些参数。
总的来说,request/index.ts 是对 baseURL 一样的请求的封装,request/request.ts 是对所有请求的封装

4. 优化

  • 因为 Easy Mock 的接口支持跨域,所以没有配到代理里去,如果是正常开发接口,还需要修改 vite.config.ts 里的 proxy。不过我们之前的教程里已有代理配置说明,这里便不再赘述
  • baseURL 还可以放在 env 变量里,以便区分开发环境和生产环境
  • ** 删除 loading,这里只是为了提供一种思路?? **

原文链接:https://www.cnblogs.com/weizwz/p/17917222.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号