一比一還原axios原始碼(六)—— 配置化

Zaking發表於2022-03-18

  上一章我們完成了攔截器的程式碼實現,這一章我們來看看配置化是如何實現的。首先,按照慣例我們來看看axios的文件是怎麼說的:

    首先我們可以可以通過axios上的defaults屬性來配置api。

  我們可以自己建立一個axios例項,傳入對應的可配置引數,然後還可以通過defaults來修改。其實就是後寫的配置,會覆蓋之前的配置。那麼接下來我們就來看程式碼吧~

  首先,我們在lib根目錄下,建立一個defaults檔案,在裡面寫下我們的預設配置:

  目前來說吼,就這麼幾個配置。 adapter這個預設配置,是用來區分宿主環境的也就是是使用xhr還是http,在我們們這裡getDefaultAdapter是這樣的:


 import adapterXHR from "./adapters/xhr";

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的取消功能。

相關文章