前端开发小技巧 -【TypeScript + Axios】- axios基本配置 + 业务失败处理 + 摘取核心数据 + 401处理 + 工具函数 + 类型设置

197 阅读4分钟

前言

  • 现在我接触的前端项目都是在用 axios 发请求的。在使用 JS 的时候,不用管类型, 我们直接使用即可,但是在使用 TS 开发的时候,axios 这块我自己觉得还是挺重要的,封装好 axios 之后(包括:拦截器、token过期、业务失败的处理、尤其是 类型设置(我们使用响应回来的数据的时候,直接点的时候会有提示,很舒服,也避免一些类型错误),在我们后面的开发中会很方便);
  • image.png

一、安装、配置axios

1.1 安装 axios

  • pnpm add axios

1.2 配置 axios

  • axios配置的文件位置:src/utils/request/index.ts
import axios from 'axios';
import { useUserStore } from '@/src/stores';
import { showToast } from 'vant';
import router from '@/router';

// axios 实例名字可自定义,根据自己公司的要求(有些是 request ,有些是 http ,有些是 interface )
const instance = axios.create({
    // 1.配置 - 基础地址 + 超时时间
    baseURL: '',
    timrout: 5000
});

instance.interceptors.request.use(
    (config) => {
        // 2.携带 token
        //  看项目的 token 是存储在哪里的(Pinia、localStorage、sessionStorage),根据存储位置的不同,选择不同的方式
        const store = useUserStore();
        // 我们首先需要判断 userInfo 中是否存在 token
        // config.headers 的类型有可能是 undefined,所以需要在给其属性赋值的时候进行类型守卫(逻辑判断)
        if (store.userInfo?.token && config.headers) {
            // 携带 token 的时候,需要知道后台需要的token格式是怎么样的,有时候需要在token前面拼接特殊的字符串;
            config.headers.Authorization = store.suerInfo.token;
        }
        
        if (sessionStorage.getItem('token') && config.headers) {
            config.headers.Authorization = sessionStorage.getItem('token');
        }
        
        return config;
    },
    (err) => Promise.reject(err);
);

instance.interceptors.response.use(
    (res) => {
        // 3.处理业务失败【HTTP状态码为200,但是返回的数据中的code为10001(正常为10000,其他表示错误)】
        if (res.data.code !== 10000) {
            // 错误的提示
            // 根据所选择的组件库的不同,选择不同的提示方式(这里以Vant的轻提示为例)
            showToast(res.data.message || '业务失败!');
            // 返回错误的Promise
            return Promise.reject(res.data);
            // 传入 code(方便后期根据不同的code在catch中给出不同的提示)
        }
        // 4.摘取核心数据(返回到数据一般都是对象包对象,如果不做处理的话,在读取属性的时候很麻烦,又容易出错)
        return res.data;
    },
    (err: AxiosError) => {
        // 5.处理 401 错误(token失效)
        // token失效的code要根据自己接口返回的写,这里是举例
        if (res.response?.status === 401) {
            // 清除本地用户信息
            const store = useUserStore();
            store.clearUserInfo();
            // 跳转到登陆页面(优化:携带当前访问页面的路径(该路径包含参数))
            router.push({
                path: '/login',
                query: {
                    // router.currentRoute - 是 ref 类型,读取属性的必须使用 .value
                    // router.currentRoute.value.fullPath - 表示当前路由的地址,包括路径,参数等等
                    returnUrl: router.currentRoute.value.fullPath
                }
            });
        }
        return Promise.reject(err);
    }
);

export default instance;

二、封装工具函数 及 类型设置

2.1 封装工具函数,定义泛型函数

  • 工具函数位置一般都是在 src/utils/request.tssrc/apis/http.ts
    • 这里以 src/utils/request.ts 为例;
// 以下方式任选一种
// 定义 泛型接口
// 这里为什么需要导出请看下面2.1.1
export interafce ResData<T> {
    data: number;
    data: T;
    message: string;
};

// 定义 泛型别名
type ResData<T> = {
    code: number;
    data: T;
    message: string;
};

// url:请求地址,字符串类型
// method:axios提供了一个类型叫做 Method(字面量联合类型),里面有各种请求类型
// submitData:有些请求,不需要向后台传递数据,所以是个可选参数
// 返回 Promise实例
export const request = <T>(url: string, method: Method = 'GET', submitData?: object) => {
    // EXPLAIN 这里为什么 第一个参数 设置为 any 类型?
        // 因为在响应拦截器中,我们对响应的数据进行了修改,原本是res,我们 return 了 res.data,axios 对 res 的中 data 的类型是 any
    // EXPLAIN axios要给自定义的数据设置类型,采用泛型第二个参数设置即可,因为第一个参数无意义,所以设置为any
    return interface<any, ResData<T>>({
        url,
        method,
        [method.toUpperCase() === 'GET' ? 'params' : 'data']: submitData
    });
}

2.1.1 ❗❗ 注意这块有坑

  • 如果定义请求工具函数泛型的时候,你使用的是 interface,后续在声明API函数的时候,会报错;
    • image.png
    • 解决
      • 定义的接口(interface) 进行 按需导出 即可;
  • 如果使用的是类型别名的方式定义泛型,在后续的使用中是不会报错的;

2.2 展示【定义类型 + 封装接口 + 调用接口】

2.2.1 定义类型文件

// 定义类型文件 - src/types/user.d.ts
// 以下方式二选一(接口 / 类型别名)
export interface UserInfo {
    username: string;
    id: string;
    avatar: string;
    mobile: string;
    account: string;
    token: string;
    refreshToken: string;
}

export type UserInfo = {
    username: string;
    id: string;
    avatar: string;
    mobile: string;
    account: string;
    token: string;
    refreshToken: string;
}

2.2.2 封装接口

// 定义获取用户信息接口
import { request } from '@/utils/request'

export const getUserInfoAPI = (data)<T> => request<T>('/app/auth/jwt/getUserInfo', 'POST', data);

2.2.3 调用接口,导入类型文件,传入类型

// 导入 类型文件
import type { UserInfo } from '@/types/user';

// 调用接口
import { getUserInfoAPI } from '@/apis/user';

const getUserInfo = async() => {
    const userInfo = await getUserInfoAPI<UserInfo>({ username: '张三', password: 'Aa123456@@'});
    // 然后我们读取 userInfo 属性的时候, . 之后就会有属性提示了
}

image.png