# Axios 配置化实现:请求与响应拦截器优化
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的请求和响应拦截器功能,并且为它们编写了对应的测试用例。接下来,我们就来实现`axios`的配置化,所谓配置化,就是指`axios`可以根据我们传入的配置项来发送不同配置的请求。那么,在`axios`中都有哪些配置项呢?我们可以先来看一下官方`axios`的一些常用的配置项。
# 2. 官方 axios 常用配置项
以下是官方`axios`常用的配置项列表:
- `url`:请求地址,必需项
- `method`:请求方法,默认为`get`
- `baseURL`:请求的基础路径,当`url`为相对路径时,会自动添加在`url`前面,除非`url`为绝对路径
- `transformRequest`:允许在请求发送前对请求数据进行修改,只适用于`put`、`post`、`patch`方法,数组中的最后一个函数必须返回一个字符串或`ArrayBuffer`或`Stream`
- `transformResponse`:允许在响应数据传递给`then/catch`前对响应数据进行修改
- `headers`:自定义请求头信息
- `params`:`URL`参数,必须是纯对象或者`URLSearchParams`对象
- `paramsSerializer`:负责`params`序列化的函数
- `data`:作为请求体发送的数据,只适用于`put`、`post`、`patch`方法
- `timeout`:请求超时时间,单位为毫秒,默认为`0`,表示永不超时
- `withCredentials`:表示跨域请求时是否需要使用凭证,默认为`false`
- `adapter`:允许自定义处理请求,这会使测试更加简单,默认为`xhr`和`http`
- `auth`:表示应该使用`HTTP`基础验证,并提供凭据
- `responseType`:表示服务器响应的数据类型,默认为`json`,可选项为`arraybuffer`、`blob`、`document`、`json`、`text`、`stream`
- `xsrfCookieName`:用作`xsrf token`值的`cookie`名称,默认为`XSRF-TOKEN`
- `xsrfHeaderName`: 带有`xsrf token`值的`http`请求头名称,默认为`X-XSRF-TOKEN`
- `onUploadProgress`:允许为上传处理进度事件
- `onDownloadProgress`:允许为下载处理进度事件
- `maxContentLength`:定义允许的响应内容的最大尺寸
- `validateStatus`:定义对于给定的`HTTP`响应状态码是`resolve`还是`reject`。如果返回`true`,`resolve`;否则`reject`
- `maxRedirects`:定义在`node.js`中重定向的最大数量,默认为`5`
- `httpAgent`和`httpsAgent`:分别在`node.js`中用于定义在执行`http`和`https`时使用的自定义代理
- `proxy`:定义代理服务器的主机名称和端口
- `cancelToken`:指定用于取消请求的`cancel token`
以上是官方`axios`的一些常用配置项,当然,我们不可能在初始版本就实现这么多配置项,我们会在后续的迭代更新中逐渐添加。在初始版本中,我们先实现一些最常用的配置项,如:`url`、`method`、`headers`、`params`、`data`、`timeout`、`withCredentials`、`responseType`等。
OK,接下来我们就来实现这些配置项。
# 3. 接口定义
由于我们是用`TypeScript`来开发`axios`,所以我们需要先定义配置项的接口类型。
我们在`src`目录下创建`types`目录,用来存放所有的类型定义文件。然后在`types`目录下创建`index.ts`文件,用来统一导出所有的类型定义。
## 3.1 定义请求方法类型
在发送请求时,我们需要指定请求方法,请求方法我们定义为字符串字面量类型,如下:
```typescript
// types/index.ts
export type Method =
| "get"
| "GET"
| "delete"
| "DELETE"
| "head"
| "HEAD"
| "options"
| "OPTIONS"
| "post"
| "POST"
| "put"
| "PUT"
| "patch"
| "PATCH";
```
## 3.2 定义请求配置接口
接下来,我们定义请求配置接口`AxiosRequestConfig`,它描述了发送请求时我们可以配置的选项,如下:
```typescript
// types/index.ts
export interface AxiosRequestConfig {
url?: string;
method?: Method;
data?: any;
params?: any;
headers?: any;
responseType?: XMLHttpRequestResponseType;
timeout?: number;
}
```
其中,每个属性的含义如下:
- `url`:请求地址,可选
- `method`:请求方法,可选,默认为`get`
- `data`:请求体数据,可选,只有当请求方法为`post`、`put`、`patch`等时才有意义
- `params`:`URL`参数,可选
- `headers`:请求头信息,可选
- `responseType`:响应数据类型,可选,默认为`json`
- `timeout`:超时时间,可选,单位为毫秒,默认为`0`,表示永不超时
## 3.3 定义响应数据接口
当服务器返回响应数据时,`axios` 需要返回一个响应数据对象,该对象包括:服务端返回的数据、HTTP 状态码、状态消息、响应头、请求配置对象、请求 XMLHttpRequest 对象实例。我们定义响应数据接口`AxiosResponse`如下:
```typescript
// types/index.ts
export interface AxiosResponse {
data: any;
status: number;
statusText: string;
headers: any;
config: AxiosRequestConfig;
request: any;
}
```
## 3.4 定义 axios 函数接口
我们之前已经实现了`axios`方法,并且它既支持传入一个参数,也支持传入两个参数,如下:
```javascript
axios({
url: "/api/add",
method: "post",
data: {
a: 1,
b: 2,
},
});
axios("/api/add", {
method: "post",
data: {
a: 1,
b: 2,
},
});
```
所以,我们需要为`axios`方法定义多种重载形式,如下:
```typescript
// types/index.ts
export interface Axios {
request(config: AxiosRequestConfig): AxiosPromise;
get(url: string, config?: AxiosRequestConfig): AxiosPromise;
delete(url: string, config?: AxiosRequestConfig): AxiosPromise;
head(url: string, config?: AxiosRequestConfig): AxiosPromise;
options(url: string, config?: AxiosRequestConfig): AxiosPromise;
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise;
}
export interface AxiosInstance extends Axios {
(config: AxiosRequestConfig): AxiosPromise;
(url: string, config?: AxiosRequestConfig): AxiosPromise;
}
```
我们定义了`Axios`接口,它描述了`axios`类中的公共方法,这些方法如下:
- `request`
- `get`
- `delete`
- `head`
- `options`
- `post`
- `put`
- `patch`
然后又定义了`AxiosInstance`接口继承`Axios`,并且它本身是一个函数,支持两种调用方式。
## 3.5 定义响应 promise 对象接口
由于`axios`函数返回的是一个`promise`对象,我们可以定义一个`AxiosPromise`接口,它继承于`Promise`这个泛型接口:
```typescript
// types/index.ts
export interface AxiosPromise extends Promise {}
```
这样的话,当`axios`返回的是`AxiosPromise`类型,那么`resolve`函数中的参数就是一个`AxiosResponse`类型。
## 3.6 定义错误信息接口
另外,我们还需要定义`axios`函数抛出的异常类型,如下:
```typescript
// types/index.ts
export interface AxiosError extends Error {
config: AxiosRequestConfig;
code?: string;
request?: any;
response?: AxiosResponse;
isAxiosError: boolean;
}
```
OK,到目前为止,我们已经把之前写的代码中用到的类型都定义好了,接下来,我们就需要根据这些类型来修改之前的代码了。
# 4. 创建 Axios 类
## 4.1 创建 Axios 类
由于`axios`是一个函数,并且它是一个混合对象,本身又是一个类,既可以直接调用,又可以使用`new`关键字创建实例,并且实例上面还有`get`、`post`等方法。所以,我们考虑把`axios`写成类的形式,然后把这个类的原型属性和实例属性混合到`axios`函数上。
我们在`src`目录下创建`core`目录,用来存放核心代码。然后在`core`目录下创建`Axios.ts`文件。
```typescript
// core/Axios.ts
import {
AxiosRequestConfig,
AxiosPromise,
AxiosResponse,
Method,
AxiosError,
} from "../types";
import { parseHeaders } from "../helpers/headers";
import { createError } from "../helpers/error";
export default class Axios {
request(config: AxiosRequestConfig): AxiosPromise {
return this.dispatchRequest(config);
}
get(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "get",
url,
})
);
}
delete(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "delete",
url,
})
);
}
head(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "head",
url,
})
);
}
options(url: string, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "options",
url,
})
);
}
post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "post",
url,
data,
})
);
}
put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "put",
url,
data,
})
);
}
patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise {
return this.request(
Object.assign(config || {}, {
method: "patch",
url,
data,
})
);
}
dispatchRequest(config: AxiosRequestConfig): AxiosPromise {
return new Promise((resolve, reject) => {
const { url, method = "get", data = null, headers, responseType, timeout } = config;
const request = new XMLHttpRequest();
if (responseType) {
request.responseType = responseType;
}
if (timeout) {
request.timeout = timeout;
}
request.open(method.toUpperCase(), url, true);
request.onreadystatechange = function handleLoad() {
if (request.readyState !== 4) {
return;
}
if (request.status === 0) {
return;
}
const responseHeaders = parseHeaders(request.getAllResponseHeaders());
const responseData = responseType !== "text" ? request.response : request.responseText;
const response: AxiosResponse = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request,
};
handleResponse(response);
};
function handleResponse(response: AxiosResponse) {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(
createError(
`Request failed with status code ${response.status}`,
config,
null,
request,
response
)
);
}
}
request.onerror = function handleError() {
reject(createError("Network Error", config, null, request));
};
request.ontimeout = function handleTimeout() {
reject(
createError(`Timeout of ${timeout} ms exceeded`, config, "ECONNABORTED", request)
);
};
Object.keys(headers).forEach((name) => {
if (data === null && name.toLowerCase() === "content-type") {
delete headers[name];
} else {
request.setRequestHeader(name, headers[name]);
}
});
request.send(data);
});
}
}
```
我们创建了`Axios`类,在类中定义了对应的方法,并且把之前写的`axios`函数的功能移植到了`dispatchRequest`方法中。
## 4.2 创建 axios 函数
创建好`Axios`类后,我们需要创建一个`axios`函数,这个函数的功能就是直接调用`Axios`类的`request`方法,并且把这个函数的原型指向`Axios`类的原型,这样`axios`函数就拥有了`Axios`类中的所有方法。另外,我们还要把`Axios`类的实例属性混合到`axios`函数上。
我们在`src`目录下创建`axios.ts`文件。
```typescript
// axios.ts
import { AxiosInstance } from "./types";
import Axios from "./core/Axios";
import { extend } from "./helpers/util";
function createInstance(): AxiosInstance {
const context = new Axios();
const instance = Axios.prototype.request.bind(context);
extend(instance, context);
return instance as AxiosInstance;
}
const axios = createInstance();
export default axios;
```
我们创建了`createInstance`函数,在这个函数内部,我们首先实例化了`Axios`类,得到一个`context`,然后创建`instance`指向`Axios`类的`request`方法,并绑定了上下文`context`;接着通过`extend`方法把`context`中的实例方法和属性拷贝到`instance`上,这样就实现了`axios`即拥有`Axios`类中的所有方法又拥有`Axios`类实例中的所有属性;最后返回`instance`,由于`instance`的类型是`AxiosInstance`,所以我们可以将其断言为`AxiosInstance`类型。
另外,我们还需要实现`extend`方法,我们在`src/helpers`目录下创建`util.ts`文件。
```typescript
// helpers/util.ts
export function extend(to: T, from: U): T & U {
for (const key in from) {
(to as T & U)[key] = from[key] as any;
}
return to as T & U;
}
```
`extend`方法的实现非常简单,就是遍历`from`上的属性,将其添加到`to`上,这里涉及到一些交叉类型的知识,大家可以自行学习。
# 5. 修改拦截器管理器
由于我们把`axios`从函数改为类的方式,所以拦截器管理器我们也需要做相应的修改。
## 5.1 定义拦截器管理器接口
首先,我们在`types/index.ts`中定义拦截器管理器接口。
```typescript
// types/index.ts
export interface AxiosInterceptorManager {
use(resolved: ResolvedFn, rejected?: RejectedFn): number;
eject(id: number): void;
}
export interface ResolvedFn {
(val: T): T | Promise;
}
export interface RejectedFn {
(error: any): any;
}
```
然后,我们在`AxiosRequestConfig`配置中添加`interceptors`属性。
```typescript
// types/index.ts
export interface AxiosRequestConfig {
// 新增
interceptors?: Interceptors;
}
// 新增
export interface Interceptors {
request: AxiosInterceptorManager;
response: AxiosInterceptorManager;
}
```
## 5.2 实现拦截器管理器类
然后,我们修改`src/core/InterceptorManager.ts`文件,用类的形式实现。
```typescript
// core/InterceptorManager.ts
import { ResolvedFn, RejectedFn } from "../types";
interface Interceptor {
resolved: ResolvedFn;
rejected?: RejectedFn;
}
export default class InterceptorManager {
private interceptors: Array | null>;
constructor() {
this.interceptors = [];
}
use(resolved: ResolvedFn, rejected?: RejectedFn): number {
this.interceptors.push({
resolved,
rejected,
});
return this.interceptors.length - 1;
}
forEach(fn: (interceptor: Interceptor) => void): void {
this.interceptors.forEach((interceptor) => {
if (interceptor !== null) {
fn(interceptor);
}
});
}
eject(id: number): void {
if (this.interceptors[id]) {
this.interceptors[id] = null;
}
}
}
```
## 5.3 在 Axios 类中添加拦截器
接下来,我们在`Axios`类中添加拦截器。
```typescript
// core/Axios.ts
import {
AxiosRequestConfig,
AxiosPromise,
AxiosResponse,
Method,
AxiosError,
Interceptors,
} from "../types";
import InterceptorManager from "./InterceptorManager";
import { parseHeaders } from "../helpers/headers";
import { createError } from "../helpers/error";
export default class Axios {
interceptors: Interceptors;
constructor() {
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
request(config: AxiosRequestConfig): AxiosPromise {
// 省略其他代码
}
// 省略其他方法
}
```
我们在`Axios`类的构造函数中初始化了`interceptors`对象,它有两个属性,`request`和`response`,都是拦截器管理器的实例。
## 5.4 实现拦截器链式调用
接下来,我们要在发送请求前先
最新文章
- 汽车安全与保养指南:电子稳定系统与滤清器维护要点
- 自动驾驶革命:激光雷达与深度学习重塑未来出行
- 汽车排气系统优化:提升燃烧效率的三大关键技术解析
- 智能座舱与车路协同:未来出行的四大变革趋势
- 汽车防腐蚀涂层技术提升车身耐久性
- 智能驾驶与电动汽车技术重塑未来出行生态
- 汽车驾驶安全技巧分享
- 动能回收技术:提升汽车能效15%-30%的关键突破
- 动力电池与激光雷达引领新能源汽车智能化革命
- 汇编语言程序设计实验:数据操作与调试技巧
- 智能汽车三大核心传感器:毫米波雷达、激光雷达与超声波技术解析
- 电动化与智能化驱动:汽车产业三大技术变革趋势
- 汽车安全三要素:帘布层、灯光信号与胎压监测解析
- 智能交通革命:自动驾驶与车联网如何重塑未来出行
- 双向充电技术:让电动汽车变身移动储能站的未来革命
- 安全驾驶指南:从座椅调整到雨天夜间行车
- 轮胎磨损影响汽车行驶安全需定期检查
- 好的,根据您的要求,我为您提炼了以下文章 **从内容到精准提炼与字数把控的创作指南** 这个标题在18-30字的要求范围内,直接点明了文章的核心主题,即如何根据内容精准地创作出符合字数要求的标题。
- 斯柯达汽车品质与创新之路
- 拖挂房车驾驶指南:牵引能力、制动系统与中置轴要点解析