当前位置 : 首页 > 维修数据

Axios 默认配置实现与合并策略解析

<|begin▁of▁sentence|># 1. 前言 在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只是实现了一个很简单的`axios`,一个最最基本的`axios`,我们还有很多功能没有实现,比如:`axios`的默认配置,拦截器,`XSRF`防御,上传下载进度监控,`HTTP`授权等等。那么从本篇文章开始,我们就来把这些功能一一实现。 本篇文章我们先来实现`axios`的默认配置。 # 2. 需求分析 我们先来看一下官方`axios`的默认配置是什么样子的。 我们可以在`node_modules/axios/lib/defaults.js`文件中看到官方`axios`的默认配置,如下所示: ```javascript var defaults = { adapter: getDefaultAdapter(), transformRequest: [function transformRequest(data, headers) { normalizeHeaderName(headers, 'Accept'); normalizeHeaderName(headers, 'Content-Type'); if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) || utils.isStream(data) || utils.isFile(data) || utils.isBlob(data) ) { return data; } if (utils.isArrayBufferView(data)) { return data.buffer; } if (utils.isURLSearchParams(data)) { setContentTypeIfNotSet(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } if (utils.isObject(data)) { setContentTypeIfNotSet(headers, 'application/json;charset=utf-8'); return JSON.stringify(data); } return data; }], transformResponse: [function transformResponse(data) { /*eslint no-param-reassign:0*/ var transformedData = data; if (typeof data === 'string') { try { transformedData = JSON.parse(data); } catch (e) { // ignore } } return transformedData; }], /** * A timeout in milliseconds to abort a request. If set to 0 (default) a * timeout is not created. */ timeout: 0, xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN', maxContentLength: -1, maxBodyLength: -1, validateStatus: function validateStatus(status) { return status >= 200 && status < 300; } }; defaults.headers = { common: { 'Accept': 'application/json, text/plain, */*' } }; utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { defaults.headers[method] = {}; }); utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { defaults.headers[method] = { 'Content-Type': 'application/x-www-form-urlencoded' } }); ``` 从上面代码中我们可以看到,官方`axios`的默认配置包括: - `adapter`:请求适配器,判断是浏览器环境还是`Node`环境从而使用不同的请求适配器; - `transformRequest`:对请求数据`data`进行转换; - `transformResponse`:对响应数据`data`进行转换; - `timeout`:请求超时时间,默认为`0`,永不超时; - `xsrfCookieName`:存储`XSRF`的`token`值的`cookie`的名称; - `xsrfHeaderName`:设置请求头中`XSRF`的`token`值对应的字段名称; - `maxContentLength`:响应内容的最大尺寸; - `maxBodyLength`:请求体最大尺寸; - `validateStatus`:验证响应状态码,默认`200-300`之间表示成功; - `headers`:不同请求方法的默认请求头; 由于我们目前实现的`axios`功能有限,所以我们不可能把官方所有的默认配置都加上,我们只实现目前我们已有功能相关的默认配置,如:`headers`、`transformRequest`、`transformResponse`、`timeout`等,其他的等后续实现了相关功能后再添加上。 OK,接下来我们就来实现这些默认配置。 # 3. 实现思路 我们实现默认配置的思路如下: 1. 我们先创建一个`defaults.ts`文件,里面存放着默认配置; 2. 在创建`axios`对象的时候,将用户传入的配置与默认配置进行合并,得到最终的配置; # 4. 创建默认配置对象 我们先在`src`目录下创建`defaults.ts`文件,文件内容如下: ```typescript import { AxiosRequestConfig } from "./types"; const defaults: AxiosRequestConfig = { 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`对象,它包含两个属性:`timeout`和`headers`。 - `timeout`:请求超时时间,我们暂时先设置为`0`; - `headers`:请求头,我们为所有请求方法都设置了`common`,即公共请求头,然后为不带数据的请求方法如`delete`、`get`等设置了空对象,为带数据的请求方法如`post`、`put`等设置了默认的`Content-Type`为`application/x-www-form-urlencoded`; # 5. 合并配置 我们创建好默认配置对象后,接下来我们就需要将默认配置对象与用户传入的配置进行合并。 在发送请求的时候,`axios`函数接收一个配置对象参数,我们需要在发送请求前,将传入的配置对象与默认配置对象进行合并。 合并的规则是:对于`headers`这类复杂对象,我们需要进行深拷贝合并,而对于`url`、`method`、`params`、`data`等普通字段,我们只需要用用户传入的配置去覆盖默认配置即可。 我们修改`src/core/xhr.ts`中的`xhr`函数,在函数内部将默认配置与用户配置进行合并,如下: ```typescript export default function xhr(config: AxiosRequestConfig): AxiosPromise { return new Promise((resolve, reject) => { const { url, method = "get", data = null, headers, responseType, timeout, } = mergeConfig(defaults, config); // 以下代码省略... }); } ``` 从上面代码中可以看到,我们从`mergeConfig`函数的返回值中解构出我们需要的属性,这个`mergeConfig`函数就是我们用来合并默认配置和用户配置的函数,该函数我们还没实现,接下来我们就来实现它。 我们在`src/core`目录下创建`mergeConfig.ts`文件,文件内容如下: ```typescript import { AxiosRequestConfig } from "../types"; import { deepMerge } 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 (typeof val2 === "object") { return deepMerge(val1, val2); } else if (typeof val2 !== "undefined") { return val2; } else if (typeof val1 === "object') { return deepMerge(val1); } else { 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; } ``` 在上面的代码中,我们定义了一个`mergeConfig`函数,它接收两个参数:`config1`和`config2`,其中`config1`表示默认配置,`config2`表示用户传入的配置。 在函数内部,我们先判断用户是否传入了配置,如果没有,则将其设置为空对象。然后我们创建一个空对象`config`,它用来存储合并后的配置。 接下来,我们先遍历`config2`,对`config2`中所有的`key`都执行`mergeField`方法;然后再遍历`config1`,如果`config2`中没有这个`key`,那么就对`config1`中的这个`key`执行`mergeField`方法。 在`mergeField`方法内部,我们会根据`key`去选择不同的合并策略,合并策略分三种: 1. 默认合并策略(`defaultStrat`):如果`config2`中有该属性,则取`config2`中的属性值,否则取`config1`中的属性值; 2. 只取`config2`中的合并策略(`fromVal2Strat`):如果`config2`中有该属性,则取`config2`中的属性值,否则不取任何值; 3. 复杂对象深度合并策略(`deepMergeStrat`):如果`config2`中有该属性,判断其是否为对象,如果是对象,则与`config1`中的属性值进行深度合并,否则取`config2`中的属性值;如果`config2`中没有该属性,则判断`config1`中的该属性是否为对象,如果是,则对其进行深度拷贝,否则直接取`config1`中的属性值; 我们根据不同的`key`选择不同的合并策略,我们把需要特殊合并策略的`key`都归类到不同的数组中,如下: - `stratKeysFromVal2`:`['url', 'params', 'data']`,这些`key`的合并策略是`fromVal2Strat`,即只取用户配置中的值; - `stratKeysDeepMerge`:`['headers']`,这些`key`的合并策略是`deepMergeStrat`,即对复杂对象进行深度合并; 对于没有归类的`key`,我们使用默认合并策略`defaultStrat`。 另外,在深度合并策略中,我们用到了`deepMerge`函数,该函数我们之前已经实现过,它是一个深拷贝函数,这里我们直接使用即可。 OK,合并配置函数实现好了以后,我们将其导入到`xhr.ts`中使用即可。 # 6. 编写 demo 接下来,我们编写 `demo` 来验证下我们的默认配置是否生效。 ```javascript // examples/defaults/app.ts import axios from "../../src/index"; axios({ url: "/api/axios/extend/post", method: "post", data: { msg: "hi", }, }); axios.request({ url: "/api/axios/extend/post", method: "post", data: { msg: "hello", }, }); axios.get("/api/axios/extend/get"); axios.options("/api/axios/extend/options"); axios.delete("/api/axios/extend/delete"); axios.head("/api/axios/extend/head"); axios.post("/api/axios/extend/post", { msg: "post" }); axios.put("/api/axios/extend/put", { msg: "put" }); axios.patch("/api/axios/extend/patch", { msg: "patch" }); ``` 然后我们在`examples/index.`中加上启动该`demo`的入口: ```
  • defaults
  • ``` 接着,在`examples`目录下创建`defaults`目录,并且在`defaults`目录下创建`index.`: ``` < lang="en"> defaults example ``` 最后,在`examples/server.js`中添加`defaults`接口: ```javascript // defaults router.post("/api/axios/extend/post", function(req, res) { res.json(req.body); }); router.get("/api/axios/extend/get", function(req, res) { res.json(req.body); }); router.options("/api/axios/extend/options", function(req, res) { res.json(req.body); }); router.delete("/api/axios/extend/delete", function(req, res) { res.json(req.body); }); router.head("/api/axios/extend/head", function(req, res) { res.json(req.body); }); router.post("/api/axios/extend/post", function(req, res) { res.json(req.body); }); router.put("/api/axios/extend/put", function(req, res) { res.json(req.body); }); router.patch("/api/axios/extend/patch", function(req, res) { res.json(req.body); }); ``` 然后`npm start`启动服务,打开浏览器,访问:`http://localhost:8000/`,接着点击 `defaults`,通过`F12`的 `network` 部分我们可以看到每个请求的请求头中都已经带上了我们设置的默认配置。 - `get`、`options`、`delete`、`head`请求的请求头中都有`Accept: application/json, text/plain, */*`; - `post`、`put`、`patch`请求的请求头中除了有`Accept: application/json, text/plain, */*`,还有`Content-Type: application/x-www-form-urlencoded`; ![](~@/axios/08/01.gif) # 7. 遗留问题 我们虽然已经实现了默认配置,但是目前还存在一个问题:我们无法修改默认配置。如果我们想修改默认配置,该怎么办呢?官方`axios`为我们提供了`axios.defaults`属性,我们可以通过修改该属性来修改默认配置。 例如: ```javascript axios.defaults.headers.common["Accept"] = "application/json, text/plain"; axios.defaults.headers.post["Content-Type"] = "application/json"; ``` 那么,我们下一篇文章就来实现如何修改默认配置。

    栏目列表