目录
  • axios 的 2 种使用方式
  • 请求方法别名
  • Axios 类
    • Axios.prototype.request()
    • axios.defaults/interceptors
    • 请求方法别名
  • 导出 axios
    • 如果直接导出 Axios
  • 总结

    本文我们将讨论 axios 对外出口 API 是如何设计的。

    axios 的 2 种使用方式

    当通过 npm install axios 安装完 axios 之后,就可以以下 2 种方式使用 axios。

    一种是在请求时传入 axios 配置项。

    // GET 请求
    axios.get('/user', {
        params: {
          ID: 12345
        }
      })
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      })
      .finally(function () {
        // always executed
      });
      
    // POST 请求
    axios.post('/user', {
        firstName: 'Fred',
        lastName: 'Flintstone'
      })
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
    

    还有一种,就是通过 axios.create() 创建一个包含预配置的 axios,再进行请求。

    const instance = axios.create({
      baseURL: 'https://some-domain.com/api/',
      timeout: 1000,
      headers: {'X-Custom-Header': 'foobar'}
    });
    
    axios.get('/user')
    axios.post('/user', {firstName: 'Fred',lastName: 'Flintstone'})
    

    这样就避免了每个请求中总是重复书写相同配置的苦恼。

    请求方法别名

    在实际项目中使用 axios 请求时,我们通过会使用 axios.method() 的方式发送请求。这样的方法一共有 8 个:

    • axios.get(url[, config])
    • axios.delete(url[, config])
    • axios.head(url[, config])
    • axios.options(url[, config])
    • axios.post(url[, data[, config]])
    • axios.put(url[, data[, config]])
    • axios.patch(url[, data[, config]])
    • axios(config)

    这 8 个请求方法,底层都是基于 axios.request(config) 封装的。

    其中:

    • axios(config) 是 axios.request(config) 的别名
    • axios.get、axios.delete、axios.head、axios.options(url[, config]) 类似于 axios.request({ …config, method, url, data: config?.data })
    • axios.post、axios.put、axios.patch(url[, data[, config]]) 类似于 axios.request({ …config, method, url, data })

    了解了这些请求方法别名后,我们就来看它们的底层实现。

    Axios 类

    axios 其实是内部 Axios 类的实例。Axios 类的源码位于 lib/core/Axios.js。

    其总体实现如下:

    // /v1.6.8/lib/core/Axios.js
    class Axios {
      // 1)
      constructor(instanceConfig) {
        this.defaults = instanceConfig;
        this.interceptors = {
          request: new InterceptorManager(),
          response: new InterceptorManager()
        };
      }
      
      // 2)
      async request(configOrUrl, config) {/* ... */}
      
      // 3)
      getUri(config) {/* ... */}
    }
    
    // 3)
    ['delete', 'get', 'head', 'options'].forEach(function forEachMethodNoData(method) {/* ... */})
    ['post', 'put', 'patch'].forEach(function forEachMethodNoData(method) {/* ... */})
    

    1):可以看到,Axios 类的实现代码还是比较少的,就 2 个方法:

    • async request() 就对应前面所说的 axios.request(),是所有请求的入口地方
    • getUri() 比较少用,是用来获得完整请求路径(基于当前 axios 的默认配置和你传入的配置)

    2):另外,在创建 Axios 实例时,实例对象上会绑定 defaults、interceptors 属性。

    • defaults 是在创建 Axios 实例传入的配置,作为默认配置用
    • interceptors 则是拦截器配置入口,如果你设置过拦截器,肯定知道它

    3):最后这部分的代码,其实就是基于 Axios.prototype.request 方法进行封装,分别在 Axios.prototype 上添加 'delete'、'get'、'head'、'options' 和 'post'、'put'、'patch' 方法,前 4 个是一类,后 3 个是一类。

    接下来,我们详细说一下。

    Axios.prototype.request()

    这是 axios 实现核心请求逻辑的方法。

    为了避免赘述,我这里给出伪代码实现。

    async request(configOrUrl, config) {
      // 获得传入的配置
      config = getFinalConfigBy(configOrUrl, config)
      
      // 与内部默认配置合并
      config = mergeConfig(this.defaults, config);
      
      // 拼装完整请求链
      const chain = []
        .concat(requestInterceptorChain)
        .concat(dispatchRequest)
        .concat(responseInterceptorChain)
      
      // 发起并返回请求结果
      return request(chain)
    }
    

    axios.defaults/interceptors

    这是 Axios 实例上的两个属性。

    axios.defaults 允许你做默认配置的修改,会对所有当前 axios 实例的请求都生效。

    axios.interceptors 则用来提供配置请求/响应拦截器。interceptors.request 允许你在请求前修改请求配置,interceptors.response 则允许你在请求得到响应后、交由用户处理前,提前统一对响应数据做处理。

    request、response 2 个属性都是 InterceptorManager 实例,提供 use()/eject()/clear()(对外) 方法和 forEach()(对内)方法。

    有兴趣的想要了解拦截器实现机制的读者,可以浏览之前的《axios 拦截器机制是如何实现的?》 一文进行学习。

    请求方法别名

    再来看看请求方法别名的实现。

    先看 'delete'、'get'、'head'、'options' 这 4 个方法。

    // /v1.6.8/lib/core/Axios.js#L193-L202
    ['delete', 'get', 'head', 'options'].forEach(function forEachMethodNoData(method) {
      Axios.prototype[method] = function(url, config) {
        return this.request(mergeConfig(config || {}, {
          method,
          url,
          data: (config || {}).data
        }));
      };
    });
    

    实现稍稍简单。本质上就是为 Axios.prototype 分别添加以上述 4 种请求方式为名的请求实现。this.request() 就是 Axios.prototype.request() 方法。

    以 axios.delete(url, config) 为例:'delete' 作为 config.method,url 作为 config.url,最后和传入的 config 合并成一个,交由 axios.request() 处理。

    再来看看 'post'、'put'、'patch' 这 3 个方法的实现。

    // /v1.6.8/lib/core/Axios.js#L204-L223
    ['post', 'put', 'patch'].forEach(function forEachMethodWithData(method) {
      function generateHTTPMethod(isForm) {
        return function httpMethod(url, data, config) {
          return this.request(mergeConfig(config || {}, {
            method,
            headers: isForm ? {
              'Content-Type': 'multipart/form-data'
            } : {},
            url,
            data
          }));
        };
      }
    
      Axios.prototype[method] = generateHTTPMethod();
      Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
    });
    

    会稍稍复杂一丢丢。因为 axios 还额外支持了 'postForm'、'putForm'、'patchForm' 3 个方法,用于传输文件的场景。

    以 axios.post(url, data, config) 为例:'post' 作为 config.method,url 作为 config.url,data 作为 config.data,最后和传入的 config 合并成一个,交由 axios.request() 处理。

    以 axios.postForm(url, data, config) 为例,相比 axios.post(url, data, config),多传入了一个 'Content-Type': 'multipart/form-data' 的请求头配置。这个请求头项会帮助后端理解要处理的请求类型。

    导出 axios

    以上我们介绍了 Axios 类的逻辑实现。

    如果直接导出 Axios

    不过,如果只导出 Axios 类作为对外输出,那么使用方式就是下面这样。

    //////
    // axios.js
    //////
    
    export { default as defaults } from './defaults/index.js';
    export default Axios;
    
    ////// 
    // app.js
    //////
    
    import Axios, { defaults } from 'axios'
    // 使用方式一
    new Axios(defaults).get('/user')
    new Axios(defaults).post('/user', {firstName: 'Fred',lastName: 'Flintstone'})
    // 使用方式二
    const createAxios = () {
      return new Axios(defaults) 
    }
    const axios = createAxios()
    axios.get('/user')
    axios.post('/user', {firstName: 'Fred',lastName: 'Flintstone'})
    

    new Axios() 创建实例的方式不够优雅,而且每次使用都会有一段样板代码。

    为了减少这部分的工作,axios 团队在导出时针对 Axios 做了一层封装,让导出 API 更加好用。源码位于 lib/axios.js。

    // /v1.6.8/lib/axios.js#L28-L44
    function createInstance(defaultConfig) {
      // 1.1)
      const context = new Axios(defaultConfig);
      const instance = Axios.prototype.request.bind(context);
    
      // 2)
      // Copy axios.prototype to instance
      utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
      // Copy context to instance
      utils.extend(instance, context, null, {allOwnKeys: true});
    
      // 3)
      // Factory for creating new instances
      instance.create = function create(instanceConfig) {
        return createInstance(mergeConfig(defaultConfig, instanceConfig));
      };
      
      // 1.2)
      return instance;
    }
    
    // 1)
    // Create the default instance to be exported
    const axios = createInstance(defaults);
    
    // this module should only have a default export
    export default axios
    

    1)、首先,导出的 axios 其实是 createInstance(defaults) 的返回值

    参数 defaults 是 axios 内部默认配置信息。

    import defaults from './defaults/index.js';
    

    而 createInstance() 内部,返回的其实并不是 Axios 实例,而是 Axios 实例的 request() 方法。

    function createInstance(defaultConfig) {
      // 返回的并不是 Axios 实例
      const context = new Axios(defaultConfig);
      // 而是 Axios 实例的 request() 方法
      const instance = Axios.prototype.request.bind(context);
      
      // ...
      
      return instance;
    }
    

    这样,我们就能以 axios(config) 方式发起请求,没必要写成 axios.request(config) 这样了。

    同时值得注意的时,request() 方法内部的 this 绑定到了 Axios 实例(即 context)上。这样,request() 方法内部访问的 this 时就不会有问题了。

    2)、不过 instance(也就是 axios.request)上现在是没有 Axios 实例上的其他方法了!

    因此,接下来我们把 Axios.prototype 和 context 上的属性都复制给 instance。

    function createInstance(defaultConfig) {
      const context = new Axios(defaultConfig);
      const instance = Axios.prototype.request.bind(context);
    
      // 把 Axios.prototype 上的属性都复制给 instance
      utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
      // 把 context 上的属性都复制给 instance
      utils.extend(instance, context, null, {allOwnKeys: true});
    
      // ...
      
      return instance;
    }
    

    如此一来,我们就能在 instance 上调用 get()/post()/getUri() 等这些方法了。

    3)另外,在来实现 axios.create()

    function createInstance(defaultConfig) {
      // ...
      
      // 基于内部默认配置,在创建一个新的 Axios 实例
      instance.create = function create(instanceConfig) {
        return createInstance(mergeConfig(defaultConfig, instanceConfig));
      };
      
      return instance;
    }
    

    可以看到 instance.create 手动增加了 create() 方法。

    create() 方法内部其实就是递归调用 createInstance()。不同的是,instance.create() 本质上是基于导出的 axios 的基础上再多一步自定义配置合并。

    总结

    本文我们讲解了 axios 对外出口 API 是如何设计的。

    我们首先介绍了内部 Axios 类的实现,这是 axios 核心逻辑所在;其次为了让出口 API 更好使用,真正导出的 axios 其实是 Axios 实例的 request() 方法,最后又在增加了 create() 方法,方便进一步得到拥有自定义配置的 axios 对象。

    以上就是axios对外出口API的设计方法的详细内容,更多关于axios对外出口API的资料请关注其它相关文章!

    声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。