美文网首页
七、实现ts-axios的默认参数设置

七、实现ts-axios的默认参数设置

作者: 雪燃归来 | 来源:发表于2020-04-30 14:13 被阅读0次

我们希望给ts-axios传入一些默认的参数,可以在没有传递参数的时候,设置一些必须的参数。本片文章,我们就来实现这个功能。

一、实现默认参数的类型(defaults.ts)

新建文件./defaults.ts

import { AxiosRequestConfig } from './types'

const defaults: AxiosRequestConfig = {
  method: 'get',
  timeout: 0,
  headers: {
    common: {
      Accept: 'appliaction/json, text/plain, */*'
    }
  }
}

const methodsNoData = ['delete', 'get', 'head', 'options']
methodsNoData.forEach(method => {
  defaults.headers[method] = {}
})

const methodsWithData = ['post', 'put', 'patch']
methodsWithData.forEach(method => {
  defaults.headers[method] = {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})

export default defaults

从上面的代码中,我们可以看出,我们对headers进行了设置,其中headers[common]中保存了一些所有接口通用的一些头信息, 并且为每个请求根据其请求方式,设置各自的专属请求头信息。比如我们有如下的请求。

import axios from '../../src/index'
import qs from 'qs'

axios.defaults.headers.common['test2'] = 123

axios({
  url: '/config/post',
  method: 'post',
  data: qs.stringify({
    a: 1,
  }),
  headers: {
    test: '321',
  },
}).then((res) => {
  console.log(res.data)
})

那么这个请求经过./default.ts处理之后,返回的值如下所示:


image.png

二、使用默认参数

修改文件的位置 ./axios.ts

import { AxiosInstance, AxiosRequestConfig } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'
+ import defaults from './defaults'
function createInstance(config: AxiosRequestConfig): AxiosInstance {
  const context = new Axios(config)
  const instance = Axios.prototype.request.bind(context)
  extend(instance, context)
  return instance as AxiosInstance
}
+ const axios = createInstance(defaults)
export default axios

三、添加到axios对象中

根据需求,我们需要在axios对象中添加一个defaults属性,表示默认配置:

export default class Axios {
  defaults: AxiosRequestConfig
  interceptors: Interceptors

  constructor(initConfig: AxiosRequestConfig) {
    this.defaults = initConfig
    this.interceptors = {
      request: new InterceptorManager<AxiosRequestConfig>(),
      response: new InterceptorManager<AxiosResponse>()
    }
  }
  // ...
} 

我们给axios添加了一个 defaults 成员属性,并且让Axios的构造函数接受一个 initConfig对象,把 initConfig赋值给 this.defaults
接着修改 createInstance 方法,支持传入 config 对象。

四、配置合并及策略

定义了默认配置之后,在每个请求发送的时候,我们就需要就需要将自定义的配置和默认的配置进行,这里的合并并不是简单的合并,针对不同的属性,我们有不同的合并策略,所以此处我们是用策略模式进行合并。
下看一个对示例代码,来明确我们应该使用哪些策略。

config1 = {
  method: 'get',
  timeout: 0,
  headers: {
    common: {
      Accept: 'application/json, text/plain, */*'
    }
  }
}
config2 = {
  url: '/config/post',
  method: 'post',
  data: {
    a: 1
  },
  headers: {
    test: '321'
  }
}
merged = {
  url: '/config/post',
  method: 'post',
  data: {
    a: 1
  },
  timeout: 0,
  headers: {
    common: {
      Accept: 'application/json, text/plain, */*'
    }
    test: '321'
  }
}

上面的代码中,我们将config1和config2进行了合并,最后得到合并后的配置merged。

合并方法

export default function mergeConfig(
  config1: AxiosRequestConfig,
  config2?: AxiosRequestConfig
): AxiosRequestConfig {
  if (!config2) {
    config2 = {}
  }

  const config = Object.create(null)

  for (let key in config2) {
    mergeField(key)
  }

  for (let key in config1) {
    if (!config2[key]) {
      mergeField(key)
    }
  }

  function mergeField(key: string): void {
    const strat = strats[key] || defaultStrat
    config[key] = strat(config1[key], config2![key])
  }

  return config
}

合并方法的整体思路就是对config1和config2中的属性遍历,执行mergeFiled方法进行合并,这里的config1代表默认配置,config2代表自定义配置。
遍历的过程中,我们会使用config2[key]这种索引的方式访问,所以需要给AxiosRequestConfig的接口添加一个字符串索引签名。

export interface AxiosRequestConfig {
  // ...

  [propName: string]: any
}

mergeField方法中,我们会针对不同的属性使用不同的合并策略。

默认合并策略

这是大部分属性的合并策略,如下:

function defaultStrat(val1: any, val2: any): any {
  return typeof val2 !== 'undefined' ? val2 : val1
}

只接收自定配置的合并策略

对于一些属性如 url、params、data,合并策略如下:

function fromVal2Strat(val1: any, val2: any): any {
  if (typeof val2 !== 'undefined') {
    return val2
  }
}

const stratKeysFromVal2 = ['url', 'params', 'data']

stratKeysFromVal2.forEach(key => {
  strats[key] = fromVal2Strat
})

因为对于 url、params、data 这些属性,默认配置显然是没有意义的,它们是和每个请求强相关的,所以我们只从自定义配置中获取。

复杂对象的合并策略

function deepMergeStrat(val1: any, val2: any): any {
  if (isPlainObject(val2)) {
    return deepMerge(val1, val2)
  } else if (typeof val2 !== 'undefined') {
    return val2
  } else if (isPlainObject(val1)) {
    return deepMerge(val1)
  } else if (typeof val1 !== 'undefined') {
    return val1
  }
}

const stratKeysDeepMerge = ['headers']

stratKeysDeepMerge.forEach(key => {
  strats[key] = deepMergeStrat
})

helpers/util.ts

export function deepMerge(...objs: any[]): any {
  const result = Object.create(null)

  objs.forEach(obj => {
    if (obj) {
      Object.keys(obj).forEach(key => {
        const val = obj[key]
        if (isPlainObject(val)) {
          if (isPlainObject(result[key])) {
            result[key] = deepMerge(result[key], val)
          } else {
            result[key] = deepMerge({}, val)
          }
        } else {
          result[key] = val
        }
      })
    }
  })
  return result
}

对于 headers 这类的复杂对象属性,我们需要使用深拷贝的方式,同时也处理了其它一些情况,因为它们也可能是一个非对象的普通值。未来我们讲到认证授权的时候,auth 属性也是这个合并策略。
最后我们在 request 方法里添加合并配置的逻辑:

config = mergeConfig(this.defaults, config)

五、flatten headers

经过合并后的配置中的 headers 是一个复杂对象,多了 common、post、get 等属性,而这些属性中的值才是我们要真正添加到请求 header 中的。
举个例子:

headers: {
  common: {
    Accept: 'application/json, text/plain, */*'
  },
  post: {
    'Content-Type':'application/x-www-form-urlencoded'
  }
}

我们需要把它压成一级的,如下:

headers: {
  Accept: 'application/json, text/plain, */*',
 'Content-Type':'application/x-www-form-urlencoded'
}

这里要注意的是,对于 common 中定义的 header 字段,我们都要提取,而对于 post、get 这类提取,需要和该次请求的方法对应。

接下来我们实现 flattenHeaders 方法。
helpers/util.ts

export function flattenHeaders(headers: any, method: Method): any {
  if (!headers) {
    return headers
  }
  headers = deepMerge(headers.common || {}, headers[method] || {}, headers)

  const methodsToDelete = ['delete', 'get', 'head', 'options', 'post', 'put', 'patch', 'common']

  methodsToDelete.forEach(method => {
    delete headers[method]
  })

  return headers
}

我们可以通过 deepMerge 的方式把 common、post 的属性拷贝到 headers 这一级,然后再把 common、post 这些属性删掉。

然后我们在真正发送请求前执行这个逻辑。
core/dispatchRequest.ts

function processConfig(config: AxiosRequestConfig): void {
  config.url = transformURL(config)
  config.headers = transformHeaders(config)
  config.data = transformRequestData(config)
  config.headers = flattenHeaders(config.headers, config.method!)
}

这样确保我们了配置中的 headers 是可以正确添加到请求 header 中的

相关文章

  • 七、实现ts-axios的默认参数设置

    我们希望给ts-axios传入一些默认的参数,可以在没有传递参数的时候,设置一些必须的参数。本片文章,我们就来实现...

  • ORCFile

    一.读写代码 二 默认参数设置 参数名                        ...

  • 【Python】自定义函数

    设置默认参数 可变参数设置,返回值为元组

  • 静态工厂模式

    java参数设置默认值,只传递想传的参数

  • ES6学习笔记_函数的扩展

    为参数设置默认值 在ES5中,是不能直接为参数设置默认值的,可以通过||的逻辑运算符来设置,但是有个问题,如果||...

  • 6-1、ts-axios配置化实现

    需求 我们在发送请求的时候,可以传入配置,来决定请求的不同行为。我们也希望ts-axios可以有默认配置,定义一些...

  • ES6的新特性

    1. 函数参数默认值 不使用ES6 为函数的参数设置默认值: functionfoo(height,color){...

  • spark优化专题

    --基础设置 1、资源参数设置: --num-executors 配置Executor的数量 默认为2 -...

  • ES6笔记 - 函数的扩展

    函数参数的默认值 ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。 与解构赋值默认值结合使用 参数默认...

  • ES6 函数扩展

    1.函数参数的默认值 Ⅰ.基本结构 为函数的参数设置默认值 参数变量是默认声明的不能用let或const再次声明 ...

网友评论

      本文标题:七、实现ts-axios的默认参数设置

      本文链接:https://www.haomeiwen.com/subject/sxczwhtx.html