一比一还原axios源码(六)—— 配置化
上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的。首先,按照惯例我们来看看axios的文档是怎么说的:
首先我们可以可以通过axios上的defaults属性来配置api。
我们可以自己创建一个axios实例,传入对应的可配置参数,然后还可以通过defaults来修改。其实就是后写的配置,会覆盖之前的配置。那么接下来我们就来看代码吧~
首先,我们在lib根目录下,创建一个defaults文件,在里面写下我们的默认配置:
目前来说吼,就这么几个配置。 adapter这个默认配置,是用来区分宿主环境的也就是是使用xhr还是http,在咱们这里getDefaultAdapter是这样的:
function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== "undefined") { // For browsers use XHR adapter // adapter = require("./adapters/xhr"); adapter = adapterXHR; } else if ( typeof process !== "undefined" && Object.prototype.toString.call(process) === "[object process]" ) { // For node use HTTP adapter // 咱们没有实现服务端的http,所以直接用xhr的 adapter = adapterXHR; // adapter = require("./adapters/http"); } return adapter; }
很简单哈,就是判断下宿主环境,因为我这里没有实现http部分,所以两个条件判断引入的都是同一个xhr文件。我们再往下看就是默认的transformRequest方法:
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)) { setContentTypeIfUnset( headers, "application/x-www-form-urlencoded;charset=utf-8" ); return data.toString(); } var isObjectPayload = utils.isObject(data); var contentType = headers && headers["Content-Type"]; if (isObjectPayload && contentType === "multipart/form-data") { return toFormData( data, new ((this.env && this.env.FormData) || FormData)() ); } else if (isObjectPayload || contentType === "application/json") { setContentTypeIfUnset(headers, "application/json"); return stringifySafely(data); } return data; }, ],
这个transformRequest做的事情比较多,首先就是调用normalizeHeaderName转换两个请求头,这个之前说过,就不多说了,再然后其实就是判断请求body的类型,然后进行一定的请求头适应和转换。transformResponse则比较简单:
transformResponse: [ function transformResponse(data) { if (utils.isString(data) && data.length) { try { return JSON.parse(data); } catch (e) { if (e.name === "SyntaxError") { throw enhanceError(e, this, "E_JSON_PARSE"); } throw e; } } return data; }, ],
这里我去掉了axios原有的transitional参数,因为这个东西可以说几乎用不太到这么细,有兴趣的话大家可以自己去看,如果你一步一步跟到了这里,那么看懂这个参数的应用肯定不在话下。
再然后就是timeout、headers和validateStatus:
timeout: 0, validateStatus: function validateStatus(status) { return status >= 200 && status < 300; }, headers: { common: { Accept: "application/json, text/plain, */*", }, },
timeout和headers不说了,validateStatus实际上就是promise走reject的条件。OK,最后我们还要给defaults的headers根据不同的方法,加一下请求头字段:
utils.forEach(["delete", "get", "head"], function forEachMethodNoData(method) { defaults.headers[method] = {}; }); utils.forEach(["post", "put", "patch"], function forEachMethodWithData(method) { defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); });
很简单,不多说。完整的代码就在这里,大家可以去看。下面我们就要来看下如何把defaults配置融合进我们的请求中去。首先,我们要找到创建axios的源头的类,也就是Axios类,我们加一点代码:
export default function Axios(config) { this.defaults = config; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; }
多了个this.defaults,没错,我们会在创建实例的时候,传入我们上面编写好的defaults,那么我们就去axios.js里面,修改下代码吧:
var axios = createInstance(defaults);
然后,我们给createInstance方法加一点东西:
instance.create = function create(instanceConfig) { return createInstance(mergeConfig(defaultConfig, instanceConfig)); };
也就是我们给生成的Axios实例instance提供一个创建实例的方法create。OK,到了这里哈,我们就可以使用这个defaults配置了,但是我们要去哪用呢?没错!就是Axios的prototype上挂载的request方法里。到目前为止,咱们稍微的小小的回顾下:首先我创建了defaults默认配置 —> 然后我在Axios类里接收配置 —> 最后,我在创建axios实例的时候把默认配置传入到Axios类里。那么我们继续,在request方法内,我们需要把创建实例时默认配置和axios实例使用时传入的配置合并一下,注意!我们之前所说的所有的“配置”都是指默认配置,默认配置是在创建实例的时候传递给Axios类使用的。
var context = new Axios(defaultConfig);
我们合并的配置是合并的默认配置和手动配置,手动配置指的是传递给axios实例的配置:
axios({ url: "/c6/post", method: "post", data: qs.stringify({ a: 1, }), headers: { test: "321", }, }).then((res) => { console.log(res.data); });
比如这个axios里传的这个对象。我们言归正传,合并下配置:
config = mergeConfig(this.defaults, config);
就可以了。这个mergeConfig,之前有说过,不多说了吼。到了现在还没完,我们还有个核心的地方,就是adapter,也就是说,我们要在哪使用这个adapter,怎么使用。之前的dispatchRequest很简单,但是这里我们需要多做一些额外的处理:
import defaults from "../defaults"; import utils from "../utils"; import transformData from "./transformData"; /** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */ export default function dispatchRequest(config) { // Ensure headers exist config.headers = config.headers || {}; // Transform request data config.data = transformData.call( config, config.data, config.headers, config.transformRequest ); // Flatten headers config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers ); utils.forEach( ["delete", "get", "head", "post", "put", "patch", "common"], function cleanHeaderConfig(method) { delete config.headers[method]; } ); var adapter = config.adapter || defaults.adapter; return adapter(config).then( function onAdapterResolution(response) { // Transform response data response.data = transformData.call( config, response.data, response.headers, config.transformResponse ); return response; }, function onAdapterRejection(reason) { return Promise.reject(reason); } ); }
首先哈,我们获取一下config的headers,然后转换一下config.data,再然后,还记不记得,defaults里的headers是一个二维深度的对象,那么我们需要把它降维,最后我们获取adapter,返回这个adapter的promise即可。到了这里,我粗略的带大家过完了配置化的整条线。其中我略过了一些不常用的源码,也有一部分工具方法没有深入的去讲,那些我个人觉得大家可以自己去看,再读文章的时候,一定要对比着源码来思考,不然的话,可能不太容易理解我说的是啥。
这章到这里就完事啦。下一章我们去实现axios的取消功能。