当前位置 : 首页 > 购买指南

Axios默认配置与用户配置合并实现

<|begin▁of▁sentence|># 1. 前言 在上一篇文章中,我们实现了`axios`的请求和响应拦截器功能,并且留了一个坑,就是`axios`的默认配置目前还没有实现混入,即我们之前是把`config`与默认配置直接合并,并没有实现真正的合并,因为默认配置里面还有很多配置项我们并没有添加到`config`上,所以接下来我们就来实现默认配置的混入。 # 2. 需求分析 在发送请求的时候,我们传入的`config`参数里面只包含部分配置,还有一部分配置我们没有传入,这部分配置我们就需要使用默认配置,例如: ```typescript axios({ method: "get", url: "/api/handleRequest", }); ``` 我们只传入了`method`和`url`,那么其他配置如`headers`、`timeout`等配置我们就需要使用默认配置。 所以,我们需要把默认配置`defaults`与用户传入的`config`进行合并,而合并规则是: - 对于`url`、`method`、`params`、`data`这些属性,用户传入的`config`优先,如果用户没传,则使用默认配置; - 对于`headers`属性,我们需要用户传入的`config`和默认配置进行合并,并且用户传入的`config`优先; # 3. 实现思路 1. 首先,我们需要在`src/defaults.ts`文件中创建一个默认配置对象`defaults`,该对象里面包含一些默认配置项,如`method`、`timeout`、`headers`等; 2. 然后,我们需要在发送请求之前,将用户传入的`config`与默认配置`defaults`进行合并,合并规则如上所述; 3. 最后,将合并后的配置作为请求的配置; # 4. 代码实现 ## 4.1 创建默认配置对象 我们在`src`目录下创建`defaults.ts`文件,在该文件内创建默认配置对象`defaults`。 ```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; ``` 我们创建了一个`defaults`对象,它包含了一些默认配置: - `method`: 默认是`get` - `timeout`: 默认是`0`,即不设置超时 - `headers`: 默认包含一个`common`属性,它里面包含`Accept`字段,另外,我们还为不需要`data`的请求方法(`delete`, `get`, `head`, `options`)的`headers`添加了一个空对象,为需要`data`的请求方法(`post`, `put`, `patch`)的`headers`添加了`Content-Type`字段,默认值是`application/x-www-form-urlencoded` ## 4.2 实现配置合并 接下来,我们需要实现配置合并,我们在`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; } ``` 代码说明: - 我们首先定义了三种合并策略: - `defaultStrat`:默认策略,如果`val2`存在则返回`val2`,否则返回`val1`; - `fromVal2Strat`:只取`val2`,如果`val2`存在则返回`val2`,否则返回`undefined`; - `deepMergeStrat`:深度合并策略,如果`val2`是普通对象,则与`val1`进行深度合并,否则如果`val2`存在则返回`val2`,否则如果`val1`是普通对象,则返回`val1`的深度拷贝,否则如果`val1`存在则返回`val1`; - 然后,我们定义了三种策略分别应用在哪些属性上: - 对于`url`、`params`、`data`这三个属性,我们使用`fromVal2Strat`策略,即只取`config2`中的值; - 对于`headers`属性,我们使用`deepMergeStrat`策略,即深度合并; - 对于其他属性,我们使用`defaultStrat`策略; - 最后,我们实现了`mergeConfig`函数,该函数接收两个配置对象`config1`和`config2`,返回合并后的配置对象。合并过程是:先遍历`config2`,对每个属性调用`mergeField`函数进行合并;然后遍历`config1`,如果`config2`中没有该属性,则调用`mergeField`函数进行合并。 ## 4.3 修改 Axios 类 接下来,我们需要修改`Axios`类,在发送请求之前,将用户传入的`config`与默认配置`defaults`进行合并。 ```typescript // src/core/Axios.ts import { AxiosRequestConfig, AxiosPromise, AxiosResponse } from "../types"; import { parseHeaders } from "../helpers/headers"; import { createError } from "../helpers/error"; import { isURLSameOrigin } from "../helpers/url"; import cookie from "../helpers/cookie"; import { isFormData } from "../helpers/util"; import defaults from "../defaults"; import mergeConfig from "./mergeConfig"; export default class Axios { defaults: AxiosRequestConfig; constructor(initConfig: AxiosRequestConfig) { this.defaults = initConfig; } request(url: any, config?: any): AxiosPromise { if (typeof url === "string") { if (!config) { config = {}; } config.url = url; } else { config = url; } config = mergeConfig(this.defaults, config); // 设置method属性,保证method属性存在,并且转为小写 config.method = config.method.toLowerCase(); // 创建请求链 const chain: any[] = [ { resolved: dispatchRequest, rejected: undefined, }, ]; // 将请求拦截器数组中的拦截器加入到请求链的前面 this.interceptors.request.forEach((interceptor) => { chain.unshift(interceptor); }); // 将响应拦截器数组中的拦截器加入到请求链的后面 this.interceptors.response.forEach((interceptor) => { chain.push(interceptor); }); // 创建一个promise,初始值为config let promise = Promise.resolve(config); // 循环请求链 while (chain.length) { const { resolved, rejected } = chain.shift()!; promise = promise.then(resolved, rejected); } return promise; } get(url: string, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithoutData("get", url, config); } delete(url: string, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithoutData("delete", url, config); } head(url: string, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithoutData("head", url, config); } options(url: string, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithoutData("options", url, config); } post(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithData("post", url, data, config); } put(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithData("put", url, data, config); } patch(url: string, data?: any, config?: AxiosRequestConfig): AxiosPromise { return this._requestMethodWithData("patch", url, data, config); } _requestMethodWithoutData( method: string, url: string, config?: AxiosRequestConfig ): AxiosPromise { return this.request( Object.assign(config || {}, { method, url, }) ); } _requestMethodWithData( method: string, url: string, data?: any, config?: AxiosRequestConfig ): AxiosPromise { return this.request( Object.assign(config || {}, { method, url, data, }) ); } } ``` 代码说明: - 我们在`Axios`类的构造函数中,将传入的`initConfig`赋值给`this.defaults`; - 在`request`方法中,我们将`this.defaults`和用户传入的`config`通过`mergeConfig`函数进行合并,得到最终的`config`; ## 4.4 修改入口文件 最后,我们需要修改入口文件`src/index.ts`,在创建`axios`实例的时候,传入默认配置`defaults`。 ```typescript // src/index.ts import { AxiosInstance } 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); export default axios; ``` # 5. 编写 demo 接下来,我们在 `examples` 目录下创建 `mergeConfig` 目录,在 `mergeConfig` 目录下创建 `index.`: ``` < lang="en"> mergeConfig demo ``` 接着再创建 `app.ts` 作为入口文件: ```typescript import axios from "../../src/index"; axios({ url: "/api/mergeConfig", method: "post", data: { a: 1, }, }); ``` 接着在 `server.js` 添加新的接口路由: ```javascript // 添加合并配置接口 router.post("/api/mergeConfig", function(req, res) { res.end(); }); ``` 最后在根目录下的 `index.` 中加上启动该 demo 的入口: ```
  • mergeConfig
  • ``` OK, 我们在命令行中执行: ```bash # 同时开启客户端和服务端 npm run server | npm start ``` 接着我们打开 `chrome` 浏览器,访问 即可访问我们的 demo 了,我们点击 `mergeConfig`,通过`F12`的 `network` 部分我们可以看到发送的请求已经带上了我们默认配置的请求头信息。 ![](~@/axios/15/01.png) 我们还可以通过传入配置来覆盖默认配置,例如: ```typescript import axios from "../../src/index"; axios({ url: "/api/mergeConfig", method: "post", data: { a: 1, }, headers: { "Content-Type": "application/json;", }, }); ``` ![](~@/axios/15/02.png) 从图中可以看到,我们传入的 `Content-Type` 覆盖了默认的 `Content-Type`。 # 6. 总结 至此,我们就实现了 `axios` 的默认配置混入功能。混入规则为: - 对于 `url`、`method`、`params`、`data` 这些属性,用户传入的 `config` 优先,如果用户没传,则使用默认配置; - 对于 `headers` 属性,我们需要用户传入的 `config` 和默认配置进行合并,并且用户传入的 `config` 优先; 在下一篇文章中,我们将实现 `axios` 的请求和响应数据转换功能。

    栏目列表