🔥 Vue3 + Vite +Typescript 封装 axios 统一管理 API 接口

·  阅读 199

axios-http.com/docs/intro

axios封装,统一处理loading,以及页面出错提示等

想要实现的效果:

  1. 可创建多个实例,自定义配置(跨域携带 cookie,token,超时设置)
  2. 请求、响应拦截器
    • 请求成功,业务状态码200,解析result
    • http请求200, 业务状态码非200,全局提示服务端的报错
    • http请求非200, 说明http请求都有问题,全局提示报错
    • http请求或者业务状态码401都做注销操作
  3. 统一处理接口 loadingrefresh、加载更多,以及三种情况对应出错

封装到项目 > src > http 文件夹下

基础封装

封装为一个类,可以创建多个实例,把可配置的属性作为变量传入我们封装的类中,例如:当我们需要用到不同的后端域名时,可以用创建实例的方式去调用,不同的域名用不同的实例一一对应

http/request.ts 文件:

import axios ,{ AxiosInstance, AxiosRequestConfig } from 'axios'

class Request {
  // axios 实例
  instanceAxiosInstance
  constructor(config: AxiosRequestConfig) {
    this.instance = axios.create(config)
  }
  request(config: AxiosRequestConfig) {
    return this.instance.request(config)
  }
}
export default Request
复制代码

http/index.ts 文件:

import Request from './request'

const http = new Request({
  baseURL: baseURL,  // 配置参数
  timeout: timeout   // 配置参数
})
export default http
复制代码

拦截器封装

  • 全局拦截器
  • 实例拦截器
  • 接口拦截器

全局拦截器

// 封装类
class Request {

  instance: AxiosInstance // axios 实例
  interceptors?: RequestInterceptors

  constructor(config: AxiosRequestConfig) {

    this.instance = axios.create(config)

    //全局拦截器
    this.instance.interceptors.request.use(
      (config: RequestConfig) => {
        console.log('全局请求拦截器')
        return config
      },
      (error) => {
        return error
      }
    )
    this.instance.interceptors.response.use(
      (response) => {
        console.log('全局响应拦截器')
        return response;
      },
      (error) => {
        return error;
      }
    )
  }
}

export default Request
复制代码

实例拦截器

http/index.ts 文件:

import Request from './request'

const http = new Request({
  timeout: 10000, // 配置参数
  interceptors: {
    requestInterceptor: config => {
      console.log('实例请求拦截器')
      return config
    },
    responseInterceptor: result => {
      console.log('实例响应拦截器')
      return result
    },
  },
})

export default http
复制代码

http/request.ts 文件:

// 封装类
class Request {

  instance: AxiosInstance // axios 实例
  interceptors?: RequestInterceptors

  constructor(config: AxiosRequestConfig) {

    this.instance = axios.create(config)
    
    //实例拦截器
    this.interceptors = config.interceptors
    this.instance.interceptors.request.use(this.interceptors?.requestInterceptor)
    this.instance.interceptors.response.use(this.interceptors?.responseInterceptor)

    //全局拦截器
    。。。
  }
}

export default Request
复制代码

接口拦截器

我们需要改造我们传入的参数的类型,因为axios提供的AxiosRequestConfig是不允许我们传入拦截器的,所以自定义RequestConfig继承AxiosRequestConfig

// types.ts
import type { AxiosRequestConfigAxiosResponse } from 'axios'
export interface RequestInterceptors {
  // 请求拦截
  requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig
  requestInterceptorsCatch?: (err: any) => any
  // 响应拦截
  responseInterceptors?: (config: AxiosResponse) => AxiosResponse
  responseInterceptorsCatch?: (err: any) => any
}
// 自定义传入的参数
export interface RequestConfig extends AxiosRequestConfig {
  interceptors?: RequestInterceptors
}
复制代码

修改http/request.ts 封装文件:

// 封装类
class Request {

  instance: AxiosInstance // axios 实例
  interceptors?: RequestInterceptors

  constructor(config: RequestConfig) { // 换成 RequestConfig

    this.instance = axios.create(config)

    //实例拦截器
    。。。

    //全局拦截器
    。。。
  }

  // 换成 RequestConfig
  request<T = any>(config: RequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {

      // 对某个接口请求单独的拦截 
      if (config.interceptors?.requestInterceptor) {
        config = config.interceptors?.requestInterceptor(config)
      }

      this.instance
        .request<any, T>(config)
        .then((response: any) => {
        
          // 对某个接口返回单独的拦截 
          if (config.interceptors?.responseInterceptor) {
            response = config.interceptors?.responseInterceptor(response)
          }

          resolve(response)
        })
        .catch((error) => {
          reject(error)
        })
    })
  }
  get<T = any>(config: RequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'GET' })
  }
  post<T = any>(config: RequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'POST' })
  }
  delete<T = any>(config: RequestConfig): Promise<T> {
    return this.request<T>({ ...config, method: 'DELETE' })
  }
}

export default Request
复制代码

api 调用:

import http from "@/utils/http";

enum Api {
    HomeData = '/xx/xx/xx/xxxx.do',
}

export const getHomeData = (param1: String) =>
    http.get({
        url: `${Api.HomeData}?param1=${param1}`,
        interceptors: {
            requestInterceptor(res) {
                console.log('接口请求拦截')
                return res
            },
            responseInterceptor(result) {
                console.log('接口响应拦截')
                return result
            },
        },
    })
复制代码

执行顺序

拦截器的执行顺序为:

接口请求拦截
全局请求拦截器
实例请求拦截器
实例响应拦截器
全局响应拦截器
接口响应拦截
复制代码

全局 loading 以及 错误处理

pinia

store 保存 loading 以及 refresh 状态统计等,store/base.ts文件:

import { defineStore } from "pinia";

export const useBaseStore = defineStore("base", {
  state: () => {
    return {
      loadingFlag: "", // Loading 加载中,Refresh 刷新中
      loadCount: 0, // 加载中:loadCount 不为0
      refreshCount: 0, // 刷新中:refreshing 不为0
      loadMore: false, // 是否处于加载更多
      loadMoreErr: false, // 是否加载更多失败
      refreshing: false, // 是否处于刷新中
      errTip: "", // 错误提示,加载出错 需页面展示出错,点击可重试
    };
  },
  getters: {
  },
  actions: {
    plusLoading(params?: { tip: string }) {
      ++this.loadCount;
      this.loadingFlag = "Loading";
    },
    minusLoading(params?: { tip: string }) {
      this.loadCount > 0 ? --this.loadCount : this.loadCount = 0;
      this.loadingFlag = "";
      params?.tip ? this.errTip = params.tip : this.errTip = "";
    },
    plusRefresh(params?: { tip: string }) {
      ++this.refreshCount;
      this.loadingFlag = "Refresh";
      this.refreshing = true;
    },
    minusRefresh(params?: { tip: string }) {
      this.refreshCount > 0 ? --this.refreshCount : (this.refreshCount = 0);
      this.loadingFlag = "";
      this.refreshing = false;
      params?.tip ? this.errTip = params.tip : this.errTip = "";
    },
    plusLoadMore(params?: { tip: string }) {
      this.loadMore = true;
    },
    minusLoadMore(params?: { tip: string }) {
      this.loadMore = false;
      this.loadMoreErr = !!params?.tip;
    },
  },
});
复制代码

全局拦截更新 store

// 异常处理
const errorHandler = (error: any) => {
  console.error('异常处理', error)
  // 增加状态码错误处理 -- 略
  Toast.fail(`${error}`);
};

// 统一处理 loading refresh loadmore 以及出错
export const updateLoadingRefresh = (config: RequestConfig, flag: string, tip?: string) => {
  if (!config.loadingFlag) return
  setTimeout(function () {
    let baseStore = useBaseStore()
    let funName=`${flag}${config.loadingFlag}`;
    baseStore[funName](tip);
  }, 0);
};

// 封装类
class Request {

  instance: AxiosInstance // axios 实例
  interceptors?: RequestInterceptors

  constructor(config: RequestConfig) {

    this.instance = axios.create(config)

    //实例拦截器
    。。。

    //全局拦截器
    this.instance.interceptors.request.use(
      (config: RequestConfig) => {
        console.log('全局请求拦截器')
        updateLoadingRefresh(config, 'plus')
        return config
      },
      (error) => {
        errorHandler(error);
        return error
      }
    )
    this.instance.interceptors.response.use(
      (response) => {
        console.log('全局响应拦截器')
        updateLoadingRefresh(response.config as RequestConfig, 'minus');
        return response;
      },
      (error) => {
        errorHandler(error);
        return error;
      }
    )
  }
}
复制代码

使用

页面:

const loadData = (loadingFlag?: LoadingFlag) => {
  let params={
    config: { 
      loadingFlag
    }
  }
  homeStore.fetchData(params)
}
const refreshData = () => {
  loadData('Refresh');
}
onMounted(() => {
  loadData('Loading');
});
复制代码

api:

export const homeApi = (params1: String, config?: RequestConfig) =>
    http.get({
        ...config,
        url: `${Api.homeApi}?params1=${params1}`,
        interceptors: {
            requestInterceptor(res) {
                console.log('------接口请求拦截')
                return res
            },
            responseInterceptor(result) {
                console.log('------接口响应拦截')
                return result
            },
        },
    })
复制代码

加载交互

展示loading,出错展示出错且可以重新加载

// 全局 layout 增加判断
<div >
      <Loading
        type="spinner"
        size="32px"
        vertical
        v-if="baseStore.loadCount != 0"
        class="loading"
      >
        加载中...
      </Loading>

      <div v-else="baseStore.loadingFlag == 'Loading' && baseStore.errTip" @click="reload()">
        {{ baseStore.errTip }}
      </div>
      <slot name="content" v-else></slot>
</div>
复制代码

刷新交互

<PullRefresh v-model="baseStore.refreshing" @refresh="refreshData">
</PullRefresh>
复制代码

对于出错进行轻提示

加载更多交互

<List
          class="list"
          v-model:loading="baseStore.loadMore"
          v-model:error="baseStore.loadMoreErr"
          :finished="xxxStore.noMoreData"
          :immediate-check="false"
          finished-text="没有更多了"
          error-text="请求失败,点击重新加载"
          @load="loadMoreData"
        >
</List>
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改