上一章我們完成了攔截器的程式碼實現,這一章我們來看看配置化是如何實現的。首先,按照慣例我們來看看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的取消功能。