一比一还原axios源码(六)—— 配置化

  上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的。首先,按照惯例我们来看看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的取消功能。

Tags: