Axios进阶:配置化与拦截器功能实现
<|begin▁of▁sentence|># 1. 前言
在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只是实现了一个很简单的`axios`,一个库如果只能发送请求那是远远不够的,我们还需要为其添加很多功能,比如:配置化、拦截器、取消请求等等。那么从这篇文章开始,我们就来为我们的`axios`添加这些功能。
# 2. 配置化
## 2.1 需求分析
到目前为止,我们的`axios`都是一个单例,一旦我们修改了`axios`的默认配置,那么接下来的所有请求都会受到影响。但是在实际项目中,我们可能希望有些请求使用默认配置,而有些请求使用自定义配置。那么,我们该如何实现呢?
我们期望`axios`对象上有一个`defaults`属性,该属性保存着默认配置,并且我们还可以在发送请求时传入配置来覆盖默认配置,并且还可以创建新的`axios`实例,在新的`axios`实例上可以传入新的配置来覆盖默认配置。
## 2.2 代码实现
### 2.2.1 创建 Axios 类
首先,我们先来创建一个`Axios`类,在该类的构造函数上添加一个`defaults`属性用来保存默认配置,并且把之前`axios`函数上的逻辑挪到该类中。
```typescript
// src/core/Axios.ts
import { AxiosRequestConfig, AxiosResponse } from "../types";
import dispatchRequest from "./dispatchRequest";
export default class Axios {
defaults: AxiosRequestConfig;
constructor(initConfig: AxiosRequestConfig) {
this.defaults = initConfig;
}
request(config: AxiosRequestConfig): AxiosPromise {
return 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,
})
);
}
}
```
### 2.2.2 修改 axios 对象
然后,我们修改`axios`对象,让它变成`Axios`类的实例,并且在`axios`对象上添加一个`create`函数,该函数可以创建一个新的`Axios`实例。
```typescript
// src/axios.ts
import { AxiosInstance, AxiosRequestConfig } from "./types";
import Axios from "./core/Axios";
import { extend } from "./helpers/util";
import defaults from "./defaults";
function createInstance(config: AxiosRequestConfig): AxiosInstance {
const context = new Axios(config);
const instance = Axios.prototype.request.bind(context);
extend(instance, context);
return instance as AxiosInstance;
}
const axios = createInstance(defaults);
axios.create = function create(config: AxiosRequestConfig) {
return createInstance(extend(defaults, config));
};
export default axios;
```
OK,我们创建了`Axios`类,并且把`axios`变成了`Axios`类的实例。另外,我们还在`axios`上添加了`create`函数,该函数可以创建一个新的`Axios`实例。
### 2.2.3 默认配置
另外,我们还需要定义一些默认配置,我们在`src`目录下创建`defaults.ts`文件,用来存放默认配置。
```typescript
// src/defaults.ts
import { AxiosRequestConfig } from "./types";
const defaults: AxiosRequestConfig = {
method: "get",
timeout: 0,
headers: {
common: {
Accept: "application/json, text/plain, */*",
},
},
};
const methodsNoData = ["delete", "get", "head", "options"];
methodsNoData.forEach((method) => {
defaults.headers[method] = {};
});
const methodsWithData = ["post", "put", "patch"];
methodsWithData.forEach((method) => {
defaults.headers[method] = {
"Content-Type": "application/x-www-form-urlencoded",
};
});
export default defaults;
```
我们定义了默认配置,包括默认的请求方法、超时时间、请求头等。并且我们还为没有`data`的请求方法和有`data`的请求方法分别设置了不同的请求头。
### 2.2.4 合并配置
我们在发送请求的时候,传入的配置可以和默认配置进行合并,合并的逻辑我们在`src/core/mergeConfig.ts`中实现。
```typescript
// src/core/mergeConfig.ts
import { AxiosRequestConfig } from "../types";
import { deepMerge, isPlainObject } from "../helpers/util";
const strats = Object.create(null);
function defaultStrat(val1: any, val2: any): any {
return typeof val2 !== "undefined" ? val2 : val1;
}
function fromVal2Strat(val1: any, val2: any): any {
if (typeof val2 !== "undefined") {
return val2;
}
}
function deepMergeStrat(val1: any, val2: any): any {
if (isPlainObject(val2)) {
return deepMerge(val1, val2);
} else if (typeof val2 !== "undefined") {
return val2;
} else if (isPlainObject(val1)) {
return deepMerge(val1);
} else if (typeof val1 !== "undefined") {
return val1;
}
}
const stratKeysFromVal2 = ["url", "params", "data"];
stratKeysFromVal2.forEach((key) => {
strats[key] = fromVal2Strat;
});
const stratKeysDeepMerge = ["headers"];
stratKeysDeepMerge.forEach((key) => {
strats[key] = deepMergeStrat;
});
export default function mergeConfig(
config1: AxiosRequestConfig,
config2?: AxiosRequestConfig
): AxiosRequestConfig {
if (!config2) {
config2 = {};
}
const config = Object.create(null);
for (let key in config2) {
mergeField(key);
}
for (let key in config1) {
if (!config2[key]) {
mergeField(key);
}
}
function mergeField(key: string): void {
const strat = strats[key] || defaultStrat;
config[key] = strat(config1[key], config2![key]);
}
return config;
}
```
合并配置的逻辑比较复杂,我们定义了三种合并策略:
- 默认策略:如果`val2`存在则返回`val2`,否则返回`val1`;
- 只取`val2`策略:如果`val2`存在则返回`val2`,否则返回`undefined`;
- 深度合并策略:如果`val2`是普通对象则深度合并,否则返回`val2`,如果`val2`不存在则返回`val1`;
然后我们根据不同的属性使用不同的合并策略,比如`url`、`params`、`data`等属性使用只取`val2`策略,`headers`使用深度合并策略,其他属性使用默认策略。
### 2.2.5 修改 dispatchRequest
最后,我们需要修改`dispatchRequest`函数,在发送请求之前,将默认配置和传入的配置进行合并。
```typescript
// src/core/dispatchRequest.ts
import { AxiosPromise, AxiosRequestConfig, AxiosResponse } from "../types";
import xhr from "./xhr";
import { buildURL } from "../helpers/url";
import { transformRequest, transformResponse } from "../helpers/data";
import { processHeaders } from "../helpers/headers";
import mergeConfig from "./mergeConfig";
export default function dispatchRequest(
config: AxiosRequestConfig
): AxiosPromise {
// 合并配置
config = mergeConfig(this.defaults, config);
// 转换URL
config.url = transformURL(config);
// 转换请求头
config.headers = transformHeaders(config);
// 转换请求数据
config.data = transformRequestData(config);
// 转换响应数据
config.data = transformResponseData(config);
return xhr(config).then(
(res) => {
return transformResponseData(res);
},
(e) => {
if (e && e.response) {
e.response = transformResponseData(e.response);
}
return Promise.reject(e);
}
);
}
function transformURL(config: AxiosRequestConfig): string {
const { url, params } = config;
return buildURL(url, params);
}
function transformRequestData(config: AxiosRequestConfig): any {
return transformRequest(config.data);
}
function transformResponseData(res: AxiosResponse): AxiosResponse {
res.data = transformResponse(res.data);
return res;
}
function transformHeaders(config: AxiosRequestConfig): any {
const { headers = {}, data } = config;
return processHeaders(headers, data);
}
```
## 2.3 demo 编写
在 `examples` 目录下创建 `config`目录,在 `config`目录下创建 `index.`:
```
< lang="en">
config example
>
```
接着再创建 `app.ts` 作为入口文件:
```typescript
import axios from "../../src/axios";
axios({
url: "/api/config",
method: "post",
data: {
a: 1,
},
}).then((res) => {
console.log(res.data);
});
```
接着在 `server/server.js` 添加新的接口路由:
```javascript
// 配置接口
router.post("/api/config", function(req, res) {
res.json(req.body);
});
```
最后在根目录下的`index.`中加上启动该`demo`的入口:
```
config
```
OK,我们在命令行中执行:
```bash
# 同时开启客户端和服务端
npm run server | npm start
```
接着我们打开 `chrome` 浏览器,访问 即可访问我们的 `demo` 了,我们点击 `config`,通过`F12`的 `network` 部分我们可以看到发送的请求,并且请求头中已经带上了我们默认配置的 `Content-Type` 字段。

我们还可以通过`axios.create`创建一个新的`axios`实例,并且传入新的配置,然后使用新的`axios`实例发送请求。
```typescript
import axios from "../../src/axios";
const instance = axios.create({
timeout: 1000,
headers: {
"X-CUSTOM-HEADER": "custom header",
},
});
instance({
url: "/api/config",
method: "post",
data: {
a: 1,
},
}).then((res) => {
console.log(res.data);
});
```
我们再次访问 点击 `config`,通过`F12`的 `network` 部分我们可以看到发送的请求,并且请求头中已经带上了我们自定义的 `X-CUSTOM-HEADER` 字段,并且超时时间也变成了 1 秒。

# 3. 拦截器
## 3.1 需求分析
拦截器是`axios`非常重要的一个特性,它可以在请求发送之前和响应返回之后做一些事情。比如:在请求发送之前,我们可以为请求添加一些公共的参数,或者在响应返回之后,我们可以对响应数据进行一些处理。
我们期望`axios`对象上有一个`interceptors`属性,该属性上有`request`和`response`两个属性,它们都是`InterceptorManager`的实例,我们可以通过`use`方法添加拦截器,并且`use`方法返回一个`id`,可以通过`eject`方法取消拦截器。
## 3.2 代码实现
### 3.2.1 定义拦截器管理器
首先,我们先来定义拦截器管理器`InterceptorManager`类,该类用来管理拦截器。
```typescript
// src/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;
}
}
}
```
我们定义了`InterceptorManager`类,它支持`use`方法添加拦截器,`forEach`方法遍历拦截器,`eject`方法取消拦截器。
### 3.2.2 修改 Axios 类
然后,我们需要修改`Axios`类,在`Axios`类中添加`interceptors`属性,并且修改`request`方法,在发送请求之前先执行请求拦截器,在接收到响应之后先执行响应拦截器。
```typescript
// src/core/Axios.ts
import {
AxiosPromise,
AxiosRequestConfig,
AxiosResponse,
ResolvedFn,
RejectedFn,
} from "../types";
import dispatchRequest from "./dispatchRequest";
import InterceptorManager from "./InterceptorManager";
interface Interceptors {
request: InterceptorManager;
response: InterceptorManager;
}
export default class Axios {
defaults: AxiosRequestConfig;
interceptors: Interceptors;
constructor(initConfig: AxiosRequestConfig) {
this.defaults = initConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
request(config: AxiosRequestConfig): AxiosPromise {
const chain: Array<{
resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise);
rejected?: RejectedFn;
}> = [
{
resolved: dispatchRequest,
rejected: undefined,
},
];
this.interceptors.request.forEach((interceptor) => {
chain.unshift(interceptor);
});
this.interceptors.response.forEach((interceptor) => {
chain.push(interceptor);
});
let promise = Promise.resolve(config);
while (chain.length) {
const { resolved, rejected } = chain.shift()!;
promise = promise.then(resolved, rejected);
}
return promise;
}
// ... 其他方法
}
```
我们在`Axios`类中添加了`interceptors`属性,该属性上有`request`和`response`两个属性,它们都是`InterceptorManager`的实例。
然后我们修改了`request`方法,在发送请求之前,我们先遍历请求拦截器,将请求拦截器添加到`chain`数组的前面,然后遍历响应拦截器,将响应拦截器添加到`chain`数组的后面。然后我们通过`Promise`链式调用的方式,依次执行`chain`数组中的函数。
### 3.2.3 修改 axios 对象
最后,我们需要修改`axios`对象,将`interceptors`属性暴露出去。
```typescript
// src/axios.ts
import { AxiosInstance, AxiosRequestConfig } from "./types";
import Axios from "./core/Axios";
import { extend } from "./helpers/util";
import defaults from "./defaults";
function createInstance(config: AxiosRequestConfig): AxiosInstance {
const context = new Axios(config);
const instance = Axios.prototype.request.bind(context);
extend(instance, context);
return instance as AxiosInstance;
}
const axios = createInstance(defaults);
axios.create = function create(config: AxiosRequestConfig) {
return createInstance(extend(defaults, config));
};
export default axios;
```
## 3.3 demo 编写
在 `examples` 目录下创建 `interceptor`目录,在 `interceptor`目录下创建 `index.`:
```
< lang="en">
interceptor example
>
```
接着再创建 `app.ts` 作为入口文件:
```typescript
import axios from "../../src/axios";
axios.interceptors.request.use((config) => {
config.headers.test += "1";
return config;
});
axios.interceptors.request.use((config) => {
config.headers.test += "2";
return config;
});
axios.interceptors.request.use((config) => {
config.headers.test += "3";
return config;
});
axios.interceptors.response.use((res) => {
res.data += "1";
return res;
});
let interceptor = axios.interceptors.response.use((res) => {
res.data += "2";
return res;
});
axios.interceptors.response.use((res) => {
res.data += "3";
return res;
});
最新文章
- 新手必学:5大驾驶技巧与雨天长途行车安全指南
- 电动汽车续航里程提升技术突破
- 汽车安全三要素:帘布层、灯光信号与胎压监测解析
- 智能温控风扇系统优化汽车能量管理
- 汽车ABS与防盗系统核心技术解析及维护指南
- 2023汽车产业三大变革:固态电池突破、自动驾驶升级、车联网生态崛起
- 安全驾驶指南:从座椅调整到雨天夜间行车
- 未来出行革命:电动化、自动驾驶与共享模式破解城市交通困境
- 风扇转动汽车引擎散热降温
- 固态电池与激光雷达引领未来:车路协同重塑智能出行
- 电动化与智能化并进:电池技术、自动驾驶与车联网的融合发展
- 车主必读:伤残赔偿与盗抢险的全面保障指南
- 充电桩建设提速:2025年车桩比1:1目标下的新能源发展新机遇
- 激光雷达助力智能汽车发展
- 出租车、私家车、新能源车最全车险保障方案解析
- 车损险全解析:保障范围、理赔流程与投保建议
- 汽车驾驶安全技巧分享
- 高压压铸技术革新汽车制造:一体化铸件引领行业变革
- 汽车四轮定位全解析:前束角与外倾角调整指南
- 宝马X5越野性能卓越适合各种地形