优雅的在vue中封装axios

600 阅读3分钟

1. 前言

在我往日的开发中,经常用到的请求工具就是axios,无论是在写react,还是vue,又或者是在react-natvie,我一直都使用的是axios。当然,在项目初期,我一般也是直接使用axios全局对象进行请求,例如:

import axios from 'axios';

const resData = await axios.get('/login',{ account: 'xxxx', password:'xxx'  });
。。。。。。

当然确实能用,但是如果在ts中,我想使api有良好的类型提示,那我就得这么写:

import axios from 'axios';
type LoginRes = {
  userInfo: {
   avatar:string;
  }
}

const resData = await axios.get<LoginRes>('/login',{ account: 'xxxx', password:'xxx'  });
。。。。。。

当然,登录api都一般只用一次就行,但是比方说如果有一些特殊的api,要在多处调用,那就不得不每次都这么写一遍:


type GetMoneyRes = {
  money:account
}

// user-info.vue //这里要调用
import axios from 'axios';

const resData = await axios.get<GetMoneyRes>('/GetMoneyRes');


// account-info.vue //这里要调用
import axios from 'axios';

const resData = await axios.get<GetMoneyRes>('/GetMoneyRes');

。。。。。。

而且,如果api 发生更改,大量的修改对我们来说也是一种痛苦,所以,一个全局api 管理是至关重要的

2. 设计

我的设计思路是,给每个类型api都做一个单独axios实例,这样方便扩展更改。当然我发现,在react-native中,一直使用同一个axios实例,有时候会出现cookie丢失的情况,这种设计也就完美的解决了这个问题。然后将所有api实例都汇集到一个大的实例当中。这样就能够在react、或者vue中使用context或者provide/inject根组件注入一个全局api实例,这样就能方便所有组件调用。设计图如下:

image.png

3. 实现

3.1 写一个方便打入axios的装饰器

当然,为了方便给每一个api实例打入axios实例,我使用一个装饰器:

// api.ts
import axios from 'axios';
import { defaultConfig } from '../config';
import { requestInterceptors } from '../interceptors/request';
import { responseError, responseSussess } from './../interceptors/response';

type ApiParams = {
  host?: string; // 
  prefix: string;
};

export const Api = ({ host, prefix }: ApiParams) => {
  return function <T extends { new (...args: any[]) }>(constructor: T) {
    const hostUrl =
      host === undefined ? (import.meta.env.VITE_API_BASE_URL as string) : ''; // vite下的开发环境配置和正式环境url配置
    const axiosInstance = axios.create({
      ...defaultConfig,
      baseURL: hostUrl + prefix,
    });

    axios.interceptors.response.use(responseSussess, responseError);
    axios.interceptors.request.use(requestInterceptors);

    return class extends constructor {
      http = axiosInstance;
    };
  };
};

// config.ts

import qs from 'qs';
import { AxiosRequestConfig } from 'axios';

export const defaultConfig: AxiosRequestConfig = {
  withCredentials: true,
  paramsSerializer: params => {
    return qs.stringify(params, { arrayFormat: 'repeat' });
  },
};

当然,这里的qs是处理数组参数的时候用的,这里就不展开了。

3.2 创建一个api

然后我们使用这个装饰器,写出一个OAthapi:

// oath.ts
import { AxiosInstance } from 'axios';
import { Api } from '../decorators/api';

type LoginRes = {
  userInfo: {
    avatar: string;
  };
};

@Api({ prefix: 'OAth' })
export class OAth {
  private http!: AxiosInstance;

  /**
   * 登录
   *
   * @param {string} account
   * @param {string} password
   * @return {*}
   * @memberof OAth
   */
  async login(account: string, password: string) {
    return (
      await this.http.post<LoginRes>('login', {
        account,
        password,
      })
    ).data;
  }
}

然后将这个api,放入总的api实例中进行管理:

// api-instance.ts
import { OAth } from './apis/login';
export class ApiInstance {
  readonly oath = new OAth();
}

3.3 写一个hook,并将实例注入到根组件

然后,我们就可以使用provide/inject根组件注入,并暴露出一个hook

// api-instance.ts
。。。。。。
const key: InjectionKey<ApiInstance> = Symbol();

export const installApi: (app: App, ...options: any[]) => any = app => {
  app.provide<ApiInstance>(key, new ApiInstance());
};

export const useApi = () => {
  return inject<ApiInstance>(key) as ApiInstance;
};
。。。。。。

// main.ts
。。。。。。
import { installApi } from './http/api-instance';
createApp(App).use(router).use(installApi).mount('#app');
。。。。。。

这样,就能在所有的组件当中使用这个hook了:

image.png

当然,react也一样,使用context注入就ok了。

代码链接(仅供参考): radium-vue-admin/admin-frontend