【项目配置】1-axios结合ts封装
发表于:2023-08-28 |

前言

相信大家平时都在项目中遇到过axios的封装,尤其是有ts的时候,有关axios类型定义及其封装的,这一次我给大家讲解一下,在ts环境中,如果封装axios,本次封装将比较具体,请慢慢食用。

安装axios和typescript依赖

1
npm i typescript axios -D

依赖安装

根目录下新建tsconfig.json文件

1
{}

编译选项

你可以通过 compilerOptions 来定制你的编译选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{
"compilerOptions": {

/* 基本选项 */
"target": "es5", // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs", // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [], // 指定要包含在编译中的库文件
"allowJs": true, // 允许编译 javascript 文件
"checkJs": true, // 报告 javascript 文件中的错误
"jsx": "preserve", // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true, // 生成相应的 '.d.ts' 文件
"sourceMap": true, // 生成相应的 '.map' 文件
"outFile": "./", // 将输出文件合并为一个文件
"outDir": "./", // 指定输出目录
"rootDir": "./", // 用来控制输出目录结构 --outDir.
"removeComments": true, // 删除编译后的所有的注释
"noEmit": true, // 不生成输出文件
"importHelpers": true, // 从 tslib 导入辅助工具函数
"isolatedModules": true, // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).

/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true, // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

/* 模块解析选项 */
"moduleResolution": "node", // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {}, // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [], // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [], // 包含类型声明的文件列表
"types": [], // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true, // 允许从没有设置默认导出的模块中默认导入。

/* Source Map Options */
"sourceRoot": "./", // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./", // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true, // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true, // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

/* 其他选项 */
"experimentalDecorators": true, // 启用装饰器
"emitDecoratorMetadata": true // 为装饰器提供元数据的支持
}
}

指定文件

你也可以显式指定需要编译的文件:

1
2
3
4
5
{
"files": [
"./some/file.ts"
]
}

你还可以使用 include 和 exclude 选项来指定需要包含的文件和排除的文件:

1
2
3
4
5
6
7
8
9
{
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}

简单配置vite.config.ts

安装依赖

1
npm i --save-dev @types/node

tscofig.json

启用

1
2
3
4
5
"moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./", // 用于解析非相对模块名称的基目录
"paths": {
"@/*": ["src/*"]
}, // 模块名到基于 baseUrl 的路径映射的列表

vite.config.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { ConfigEnv, UserConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import path from 'path';

const CWD = process.cwd();

// https://vitejs.dev/config/
export default ({ mode }: ConfigEnv): UserConfig => {
return {
plugins: [
vue(),
],
base: './',
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
// server: {
// port: 3002,
// host: '0.0.0.0',
// proxy: {
// '/api': 'http://127.0.0.1:3000/',
// },
// },
};
};

测试ts是否正常编译

我适当修改了helloworld的代码
测试ts修改代码
项目编译正常,就说明我们的ts已经成功导入了
项目编译

配置axios

申明请求配置

  • src/types/axios.d.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import { AxiosRequestConfig } from 'axios';
    // 请求参数
    export interface RequestOptions {
    apiUrl?: string;
    isJoinPrefix?: boolean;
    urlPrefix?: string;
    joinParamsToUrl?: boolean;
    formatDate?: boolean;
    isTransformResponse?: boolean;
    isReturnNativeResponse?: boolean;
    ignoreRepeatRequest?: boolean;
    joinTime?: boolean;
    withToken?: boolean;
    retry?: {
    count: number;
    delay: number;
    };
    }
    // 返回结果
    export interface Result<T = any> {
    code: number;
    data: T;
    message?: string | null;
    }
    // 重复请求
    export interface AxiosRequestConfigRetry extends AxiosRequestConfig {
    retryCount?: number;
    }

数据转化

  • src/utils/request/AxiosTransform.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    import type { AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
    import { AxiosError } from 'axios';
    import type { RequestOptions, Result } from '@/types/axios';
    // 创建Axios选项
    export interface CreateAxiosOptions extends AxiosRequestConfig {
    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
    authenticationScheme?: string;
    // 数据处理
    transform?: AxiosTransform;
    // 请求选项
    requestOptions?: RequestOptions;
    }
    // Axios 数据处理
    export abstract class AxiosTransform {
    // 请求前Hook
    beforeRequestHook?: (config: AxiosRequestConfig, options: RequestOptions) => AxiosRequestConfig;

    // 转换前Hook
    transformRequestHook?: (res: AxiosResponse<Result>, options: RequestOptions) => any;

    // 请求失败处理
    requestCatchHook?: (e: Error | AxiosError, options: RequestOptions) => Promise<any>;

    // 请求前的拦截器
    requestInterceptors?: (config: AxiosRequestConfig, options: CreateAxiosOptions) => InternalAxiosRequestConfig;

    // 请求后的拦截器
    responseInterceptors?: (res: AxiosResponse) => AxiosResponse;

    // 请求前的拦截器错误处理
    requestInterceptorsCatch?: (error: AxiosError) => void;

    // 请求后的拦截器错误处理
    responseInterceptorsCatch?: (error: AxiosError) => void;
    }

请求取消

  • src/utils/request/AxiosCancel.ts

安装依赖

安装loadash

1
npm i lodash

安装loadash的type

1
npm i @types/lodash --save

请求取消代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import type { AxiosRequestConfig, Canceler } from 'axios';
import axios from 'axios';
import isFunction from 'lodash/isFunction';

// 存储请求与取消令牌的键值对列表
let pendingMap = new Map<string, Canceler>();

export const getPendingUrl = (config: AxiosRequestConfig) => [config.method, config.url].join('&');

export class AxiosCanceler {
// 添加请求到列表
addPending(config: AxiosRequestConfig) {
this.removePending(config);
const url = getPendingUrl(config);
config.cancelToken =
config.cancelToken ||
new axios.CancelToken((cancel) => {
if (!pendingMap.has(url)) {
// 如果当前没有相同请求就添加
pendingMap.set(url, cancel);
}
});
}

// 清空所有请求
removeAllPending() {
pendingMap.forEach((cancel) => {
if (cancel && isFunction(cancel)) cancel();
});
pendingMap.clear();
}

// 移除某个请求
removePending(config: AxiosRequestConfig) {
const url = getPendingUrl(config);

if (pendingMap.has(url)) {
// If there is a current request identifier in pending,
// the current request needs to be cancelled and removed
const cancel = pendingMap.get(url);
if (cancel) cancel(url);
pendingMap.delete(url);
}
}

reset() {
pendingMap = new Map<string, Canceler>();
}
}

请求配置

全局申明

  • src/types/global.d.ts
    1
    2
    3
    4
    5
    6
    7
    8
    // 通用声明
    // Vue
    declare module '*.vue' {
    import { DefineComponent } from 'vue';
    const component: DefineComponent<{}, {}, any>;
    export default component;
    }
    declare type Recordable<T = any> = Record<string, T>;

请求配置

  • src/utils/request/utils.ts
    比如我们请求需要追加一个时间戳等操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import isString from 'lodash/isString';
    import isObject from 'lodash/isObject';

    const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';

    export function joinTimestamp<T extends boolean>(join: boolean, restful: T): T extends true ? string : object;

    export function joinTimestamp(join: boolean, restful = false): string | object {
    if (!join) {
    return restful ? '' : {};
    }
    const now = new Date().getTime();
    if (restful) {
    return `?_t=${now}`;
    }
    return { _t: now };
    }

    // 格式化提交参数时间
    export function formatRequestDate(params: Recordable) {
    if (Object.prototype.toString.call(params) !== '[object Object]') {
    return;
    }

    for (const key in params) {
    // eslint-disable-next-line no-underscore-dangle
    if (params[key] && params[key]._isAMomentObject) {
    params[key] = params[key].format(DATE_TIME_FORMAT);
    }
    if (isString(key)) {
    const value = params[key];
    if (value) {
    try {
    params[key] = isString(value) ? value.trim() : value;
    } catch (error: any) {
    throw new Error(error);
    }
    }
    }
    if (isObject(params[key])) {
    formatRequestDate(params[key]);
    }
    }
    }

    // 将对象转为Url参数
    export function setObjToUrlParams(baseUrl: string, obj: object): string {
    let parameters = '';
    for (const key in obj) {
    parameters += `${key}=${encodeURIComponent(obj[key])}&`;
    }
    parameters = parameters.replace(/&$/, '');
    return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters;
    }

处理请求内部详情

安装依赖

1
npm i qs

定义请求头

  • src/utils/constants/index.ts
    1
    2
    3
    4
    5
    6
    // 通用请求头
    export enum ContentTypeEnum {
    Json = 'application/json;charset=UTF-8',
    FormURLEncoded = 'application/x-www-form-urlencoded;charset=UTF-8',
    FormData = 'multipart/form-data;charset=UTF-8',
    }

处理请求详情

  • src/utils/request/Axios.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    import axios, { AxiosRequestConfig, InternalAxiosRequestConfig, AxiosInstance, AxiosResponse, AxiosError } from 'axios';
    import { stringify } from 'qs';
    import isFunction from 'lodash/isFunction';
    import cloneDeep from 'lodash/cloneDeep';
    import { CreateAxiosOptions } from './AxiosTransform';
    import { AxiosCanceler } from './AxiosCancel';
    import { AxiosRequestConfigRetry, RequestOptions, Result } from '@/types/axios';
    import { ContentTypeEnum } from '@/constants';

    // Axios模块
    export class VAxios {
    // axios句柄
    private instance: AxiosInstance;

    // axios选项
    private readonly options: CreateAxiosOptions;

    constructor(options: CreateAxiosOptions) {
    this.options = options;
    this.instance = axios.create(options);
    this.setupInterceptors();
    }

    // 创建axios句柄
    private createAxios(config: CreateAxiosOptions): void {
    this.instance = axios.create(config);
    }

    // 获取数据处理
    private getTransform() {
    const { transform } = this.options;
    return transform;
    }

    // 获取句柄
    getAxios(): AxiosInstance {
    return this.instance;
    }

    // 配置 axios
    configAxios(config: CreateAxiosOptions) {
    if (!this.instance) {
    return;
    }
    this.createAxios(config);
    }

    // 设置通用头信息
    setHeader(headers: Record<string, string>): void {
    if (!this.instance) {
    return;
    }
    Object.assign(this.instance.defaults.headers, headers);
    }

    // 设置拦截器
    private setupInterceptors() {
    const transform = this.getTransform();
    if (!transform) {
    return;
    }
    const { requestInterceptors, requestInterceptorsCatch, responseInterceptors, responseInterceptorsCatch } =
    transform;
    const axiosCanceler = new AxiosCanceler();

    // 请求配置处理
    this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
    // @ts-ignore
    const { ignoreRepeatRequest } = config.requestOptions;
    const ignoreRepeat = ignoreRepeatRequest ?? this.options.requestOptions?.ignoreRepeatRequest;
    if (!ignoreRepeat) axiosCanceler.addPending(config);

    if (requestInterceptors && isFunction(requestInterceptors)) {
    config = requestInterceptors(config, this.options);
    }

    return config;
    }, undefined);

    // 请求错误处理
    if (requestInterceptorsCatch && isFunction(requestInterceptorsCatch)) {
    this.instance.interceptors.request.use(undefined, requestInterceptorsCatch);
    }

    // 响应结果处理
    this.instance.interceptors.response.use((res: AxiosResponse) => {
    if (res) axiosCanceler.removePending(res.config);
    if (responseInterceptors && isFunction(responseInterceptors)) {
    res = responseInterceptors(res);
    }
    return res;
    }, undefined);

    // 响应错误处理
    if (responseInterceptorsCatch && isFunction(responseInterceptorsCatch)) {
    this.instance.interceptors.response.use(undefined, responseInterceptorsCatch);
    }
    }

    // 支持Form Data
    supportFormData(config: AxiosRequestConfig) {
    const headers = config.headers || this.options.headers;
    const contentType = headers?.['Content-Type'] || headers?.['content-type'];

    if (
    contentType !== ContentTypeEnum.FormURLEncoded ||
    !Reflect.has(config, 'data') ||
    config.method?.toUpperCase() === 'GET'
    ) {
    return config;
    }

    return {
    ...config,
    data: stringify(config.data, { arrayFormat: 'brackets' }),
    };
    }

    get<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'GET' }, options);
    }

    post<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'POST' }, options);
    }

    put<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'PUT' }, options);
    }

    delete<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'DELETE' }, options);
    }

    patch<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    return this.request({ ...config, method: 'PATCH' }, options);
    }

    // 请求
    async request<T = any>(config: AxiosRequestConfigRetry, options?: RequestOptions): Promise<T> {
    let conf: CreateAxiosOptions = cloneDeep(config);
    const transform = this.getTransform();

    const { requestOptions } = this.options;

    const opt: RequestOptions = { ...requestOptions, ...options };

    const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
    conf = beforeRequestHook(conf, opt);
    }
    conf.requestOptions = opt;

    conf = this.supportFormData(conf);

    return new Promise((resolve, reject) => {
    this.instance
    .request<any, AxiosResponse<Result>>(!config.retryCount ? conf : config)
    .then((res: AxiosResponse<Result>) => {
    if (transformRequestHook && isFunction(transformRequestHook)) {
    try {
    const ret = transformRequestHook(res, opt);
    resolve(ret);
    } catch (err) {
    reject(err || new Error('请求错误!'));
    }
    return;
    }
    resolve(res as unknown as Promise<T>);
    })
    .catch((e: Error | AxiosError) => {
    if (requestCatchHook && isFunction(requestCatchHook)) {
    reject(requestCatchHook(e, opt));
    return;
    }
    if (axios.isAxiosError(e)) {
    // 在这里重写Axios的错误信息
    }
    reject(e);
    });
    });
    }
    }

在以上的基础之上,便可以将我们的请求需要的内容组装起来即可

请求整合

前缀,token名配置

  • src/config/global.ts
    1
    2
    export const prefix = 'codesigner';
    export const TOKEN_NAME = 'codesigner';

地址配置

-src/config/proxy.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export default {
isRequestProxy: true,
development: {
// 开发环境接口请求
host: '',
// host: 'http://192.168.1.98:8081/',
// 开发环境 cdn 路径
cdn: '',
},
test: {
// 测试环境接口地址
host: '',
// 测试环境 cdn 路径
cdn: '',
},
release: {
// 正式环境接口地址
host: '',
// 正式环境 cdn 路径
cdn: '',
},
};

整合代码

  • src/utils/request/index.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    // axios配置  可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
    import isString from 'lodash/isString';
    import merge from 'lodash/merge';
    import type { InternalAxiosRequestConfig } from 'axios';
    import type { AxiosTransform, CreateAxiosOptions } from './AxiosTransform';
    import { VAxios } from './Axios';
    import proxy from '@/config/proxy';
    import { joinTimestamp, formatRequestDate, setObjToUrlParams } from './utils';
    import { TOKEN_NAME } from '@/config/global';
    import { ContentTypeEnum } from '@/constants';

    const env = import.meta.env.MODE || 'development';

    // 如果是mock模式 或 没启用直连代理 就不配置host 会走本地Mock拦截 或 Vite 代理
    const host = env === 'mock' || !proxy.isRequestProxy ? '' : proxy[env].host;

    // 数据处理,方便区分多种处理方式
    const transform: AxiosTransform = {
    // 处理请求数据。如果数据不是预期格式,可直接抛出错误
    transformRequestHook: (res, options) => {
    const { isTransformResponse, isReturnNativeResponse } = options;

    // 如果204无内容直接返回
    const method = res.config.method?.toLowerCase();
    if (res.status === 204 || method === 'put' || method === 'patch') {
    return res;
    }

    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
    if (isReturnNativeResponse) {
    return res;
    }
    // 不进行任何处理,直接返回
    // 用于页面代码可能需要直接获取code,data,message这些信息时开启
    if (!isTransformResponse) {
    return res.data;
    }

    // 错误的时候返回
    const { data } = res;
    if (!data) {
    throw new Error('请求接口错误');
    }

    // 这里 code为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
    const { code, message } = data;

    // 这里逻辑可以根据项目进行修改
    const hasSuccess = data && code === 200;
    if (hasSuccess) {
    return data.data;
    }
    throw new Error(`请求接口错误, 错误码: ${code},错误提示:${message}`);
    },

    // 请求前处理配置
    beforeRequestHook: (config, options) => {
    const { apiUrl, isJoinPrefix, urlPrefix, joinParamsToUrl, formatDate, joinTime = true } = options;

    // 添加接口前缀
    if (isJoinPrefix && urlPrefix && isString(urlPrefix)) {
    config.url = `${urlPrefix}${config.url}`;
    }

    // 将baseUrl拼接
    if (apiUrl && isString(apiUrl)) {
    config.url = `${apiUrl}${config.url}`;
    }
    const params = config.params || {};
    const data = config.data || false;

    if (formatDate && data && !isString(data)) {
    formatRequestDate(data);
    }
    if (config.method?.toUpperCase() === 'GET') {
    if (!isString(params)) {
    // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
    config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
    } else {
    // 兼容restful风格
    config.url = `${config.url + params}${joinTimestamp(joinTime, true)}`;
    config.params = undefined;
    }
    } else if (!isString(params)) {
    if (formatDate) {
    formatRequestDate(params);
    }
    if (
    Reflect.has(config, 'data') &&
    config.data &&
    (Object.keys(config.data).length > 0 || data instanceof FormData)
    ) {
    config.data = data;
    config.params = params;
    } else {
    // 非GET请求如果没有提供data,则将params视为data
    config.data = params;
    config.params = undefined;
    }
    if (joinParamsToUrl) {
    config.url = setObjToUrlParams(config.url as string, { ...config.params, ...config.data });
    }
    } else {
    // 兼容restful风格
    config.url += params;
    config.params = undefined;
    }
    return config;
    },

    // 请求拦截器处理
    requestInterceptors: (config, options) => {
    // 请求之前处理config
    const token = localStorage.getItem(TOKEN_NAME);
    if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
    // jwt token
    (config as Recordable).headers.Authorization = options.authenticationScheme
    ? `${options.authenticationScheme} ${token}`
    : token;
    }
    return config as InternalAxiosRequestConfig;
    },

    // 响应拦截器处理
    responseInterceptors: (res) => {
    return res;
    },

    // 响应错误处理
    responseInterceptorsCatch: (error: any) => {
    const { config } = error;
    if (!config || !config.requestOptions.retry) return Promise.reject(error);

    config.retryCount = config.retryCount || 0;

    if (config.retryCount >= config.requestOptions.retry.count) return Promise.reject(error);

    config.retryCount += 1;

    const backoff = new Promise((resolve) => {
    setTimeout(() => {
    resolve(config);
    }, config.requestOptions.retry.delay || 1);
    });
    config.headers = { ...config.headers, 'Content-Type': ContentTypeEnum.Json };
    return backoff.then((config) => request.request(config));
    },
    };

    function createAxios(opt?: Partial<CreateAxiosOptions>) {
    return new VAxios(
    merge(
    <CreateAxiosOptions>{
    // https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
    // 例如: authenticationScheme: 'Bearer'
    authenticationScheme: '',
    // 超时
    timeout: 10 * 1000,
    // 携带Cookie
    // withCredentials: true,
    // 头信息
    headers: { 'Content-Type': ContentTypeEnum.Json },
    // 数据处理方式
    transform,
    // 配置项,下面的选项都可以在独立的接口请求中覆盖
    requestOptions: {
    // 接口地址
    apiUrl: host,
    // 是否自动添加接口前缀
    // isJoinPrefix: true,
    // 接口前缀
    // 例如: https://www.baidu.com/api
    // urlPrefix: '/api'
    // urlPrefix: '/api',
    // 是否返回原生响应头 比如:需要获取响应头时使用该属性
    isReturnNativeResponse: false,
    // 需要对返回数据进行处理
    isTransformResponse: true,
    // post请求的时候添加参数到url
    joinParamsToUrl: false,
    // 格式化提交参数时间
    formatDate: false,
    // 是否加入时间戳
    joinTime: false,
    // 忽略重复请求
    ignoreRepeatRequest: true,
    // 是否携带token
    withToken: true,
    // 重试
    retry: {
    count: 3,
    delay: 1000,
    },
    },
    },
    opt || {},
    ),
    );
    }
    export const request = createAxios();

测试请求

这里以cnode提供的api接口进行测试
cnode的api地址

写请求方式

请求方法

  • src/api/test.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import {request} from '@/utils/request';
    import type {QueryTopicParam} from "./model/testModel"
    const Api={
    list:'/topics', // 分页查询列表
    }

    export function queryTopicsByPage(param:QueryTopicParam) {
    return request.get<unknown>({
    url: Api.list,
    params: param,
    });
    }

请求类型定义

  • src/api/model/testModel.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // page Number 页数
    // tab String 主题分类。目前有 ask share job good
    // limit Number 每一页的主题数量
    // mdrender String 当为 false 时,不渲染。默认为 true,渲染出现的所有 markdown 格式文本。
    export interface QueryTopicParam {
    page: number;
    tab?: 'ask' | 'share' | 'job' | 'good';
    limit: number;
    mdrender?: 'true' | 'false';
    }

请求调用

  • src/components/HelloWorld.vue
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <script setup lang="ts">
    import { ref,onMounted } from 'vue'
    import {queryTopicsByPage} from "@/api/test";

    interface IMsg {
    msg:string
    }
    defineProps<IMsg>()

    onMounted(() => {
    queryTopicsByPage({page:1,limit:10}).then(res=>{
    console.log(res)
    })
    })

    const count = ref(0)
    </script>

请求结果

请求结果
请求结果

处理报错

我们可以看到,我们的接口是请求通过了,但是并没有处理成功。这是因为我们定义的返回字段与这个不符合,需要去手动修改下
修改返回内容
修改返回内容
处理完之后,我们就可以打印出我们想要的值了
修改之后效果

定义返回字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export interface QueryTopicResult {
id: string;
author_id: string;
tab: string;
content: string;
title: string;
last_reply_at: string;
good: boolean;
top: boolean;
reply_count: number;
visit_count: number;
create_at: string;
author:{
loginname: string;
avatar_url: string;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
import {request} from '@/utils/request';
import type {QueryTopicParam,QueryTopicResult} from "./model/testModel"
const Api={
list:'/topics', // 分页查询列表
}

export function queryTopicsByPage(param:QueryTopicParam) {
return request.get<QueryTopicResult>({
url: Api.list,
params: param,
});
}

此时,我们在vue文件中就可以看到ts的提示了
ts提示

好了,本文就分享到这里了,封装代码我放在了我的git仓库下期再见!

上一篇:
组件记录-悬浮球
下一篇:
【可视化学习】33-GEOJson生成可视化地图