合并配置

需求分析

支持默认配置和传入配置合并

  1. axios.default.timeout = 2000
  2. axios.default.headers.common.test1 = 111
  3. axios.default.headers.post.test2 = 2222
  4. axios({
  5. url: '/config/post',
  6. method: 'post',
  7. headers: {
  8. test3: '3333',
  9. },
  10. data: qs.stringify({
  11. name: '1'
  12. })
  13. })

默认配置定义

  1. import { AxiosRequestConfig } from "./types";
  2. const defaultConfig: AxiosRequestConfig = {
  3. timeout: 0,
  4. headers: {
  5. common: {
  6. Accept: 'application/json, text/plain, */*'
  7. }
  8. }
  9. }
  10. const methodsWithNoData = ['get', 'delete', 'options', 'head']
  11. methodsWithNoData.forEach(method => [
  12. defaultConfig.headers[method] = {}
  13. ])
  14. const methodsWithData = ['post', 'patch', 'put']
  15. methodsWithData.forEach(method => [
  16. defaultConfig.headers[method] = {
  17. 'Content-Type': 'application/x-www-form-urlencoded'
  18. }
  19. ])
  20. export default defaultConfig

添加axios属性

  1. export default class Axios {
  2. default: AxiosRequestConfig
  3. constructor(initConfig: AxiosRequestConfig) {
  4. this.default = initConfig
  5. }
  6. }

合并策略

  • method, timeout等用传入配置覆盖默认配置
  • url, data, params只取传入配置
  • headers 深合并对象 ```typescript import { deepMerge, isPlainObject } from “../helpers/util”; import { AxiosRequestConfig } from “../types”;

interface MergeConfig { [key: string]: (val1: any, val2: any) => void }

const mergeMap: MergeConfig = { valueFromConfig2 } // 【默认合并策略】如果config2[key] 有数据,取config2[key],否则取config1[key],如 method function defaultToConfig2(val1: any, val2: any) { return val2 !== undefined ? val2 : val1 }

// 只取config2数据, 如:url,data,params function valueFromConfig2(val1: any, val2: any) { return val2 }

const valueFromConfig2Keys = [‘url’, ‘params’, ‘data’] valueFromConfig2Keys.forEach(key => { mergeMap[key] = valueFromConfig2 })

// 对象深合并, 如headers function deepMergeValue(val1: any, val2: any) { if (isPlainObject(val1) && isPlainObject(val2)) { return deepMerge(val1, val2) } else if (isPlainObject(val1)) { return deepMerge(val1) } else if (isPlainObject(val2)) { return mergeConfig(val2) }

return val2 }

const deepMergeValueKeys = [‘headers’] deepMergeValueKeys.forEach(key => { mergeMap[key] = deepMergeValue })

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

const config = Object.create(null)

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

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

function mergeValue(key: string) { const merge = mergeMap[key] || defaultToConfig2 config[key] = merge(config1[key], config2![key]) }

return config }

  1. 定义deepMerge函数
  2. ```typescript
  3. /* obj1,obj2, obj3... */
  4. export function deepMerge(...objs: any[]): any {
  5. const result = Object.create(null)
  6. objs.forEach(obj => {
  7. Object.keys(obj).forEach(key => {
  8. const val = obj[key]
  9. if (isPlainObject(val)) {
  10. // 如果已经存在值,再拿出来合并一次
  11. if (isPlainObject(result[key])) {
  12. result[key] = deepMerge(result[key], val)
  13. } else {
  14. result[key] = deepMerge(val)
  15. }
  16. } else {
  17. result[key] = val
  18. }
  19. })
  20. })
  21. return result
  22. }

Axios.request里调用, 合并配置

  1. config = mergeConfig(this.default, config)

取出headers数据

调用 flattenHeaders

  1. function processConfig(config: AxiosRequestConfig): AxiosRequestConfig {
  2. config.url = transformUrl(config)
  3. // 在处理data之前处理headers
  4. config.headers = transformHeader(config)
  5. config.data = transformRequestData(config)
  6. // transformHeader 为了当 datas是object时,添加content-type:application/json之后在flattenHeaders
  7. config.data = flattenHeaders(config.headers, config.method!)
  8. return config
  9. }
  1. export function flattenHeaders(headers: any, method: string): any {
  2. if (!headers) {
  3. return headers
  4. }
  5. headers = deepMerge(headers.common, headers[method], headers)
  6. const methods = ['common', 'get', 'delete', 'options', 'head', 'post', 'put', 'patch']
  7. methods.forEach(method => {
  8. delete headers[method]
  9. })
  10. return headers
  11. }

官方实现方法

  1. 'use strict';
  2. var utils = require('../utils');
  3. /**
  4. * Config-specific merge-function which creates a new config-object
  5. * by merging two configuration objects together.
  6. *
  7. * @param {Object} config1
  8. * @param {Object} config2
  9. *
  10. * @returns {Object} New object resulting from merging config2 to config1
  11. */
  12. module.exports = function mergeConfig(config1, config2) {
  13. // eslint-disable-next-line no-param-reassign
  14. config2 = config2 || {};
  15. var config = {};
  16. // 值合并方法 把targe合并进source,返回source
  17. function getMergedValue(target, source) {
  18. if (utils.isPlainObject(target) && utils.isPlainObject(source)) {
  19. return utils.merge(target, source);
  20. } else if (utils.isPlainObject(source)) {
  21. return utils.merge({}, source);
  22. } else if (utils.isArray(source)) {
  23. return source.slice();
  24. }
  25. return source;
  26. }
  27. // 默认合并策略
  28. function mergeDeepProperties(prop) {
  29. if (!utils.isUndefined(config2[prop])) {
  30. // config2 有值
  31. return getMergedValue(config1[prop], config2[prop]);
  32. } else if (!utils.isUndefined(config1[prop])) {
  33. // config1 有值, config2 无值
  34. return getMergedValue(undefined, config1[prop]);
  35. }
  36. }
  37. // 只取config2, 如: url, data,method
  38. function valueFromConfig2(prop) {
  39. if (!utils.isUndefined(config2[prop])) {
  40. return getMergedValue(undefined, config2[prop]);
  41. }
  42. }
  43. // 【互斥取值】,优先config2
  44. // 如:timeout, baseURL...
  45. function defaultToConfig2(prop) {
  46. if (!utils.isUndefined(config2[prop])) {
  47. return getMergedValue(undefined, config2[prop]);
  48. } else if (!utils.isUndefined(config1[prop])) {
  49. return getMergedValue(undefined, config1[prop]);
  50. }
  51. }
  52. // 【互斥取值】,优先config2,和defaultToConfig2不同的是,这里即使传入空,也会覆盖
  53. function mergeDirectKeys(prop) {
  54. if (prop in config2) {
  55. return getMergedValue(config1[prop], config2[prop]);
  56. } else if (prop in config1) {
  57. return getMergedValue(undefined, config1[prop]);
  58. }
  59. }
  60. // 合并策略
  61. var mergeMap = {
  62. 'url': valueFromConfig2,
  63. 'method': valueFromConfig2,
  64. 'data': valueFromConfig2,
  65. 'baseURL': defaultToConfig2,
  66. 'transformRequest': defaultToConfig2,
  67. 'transformResponse': defaultToConfig2,
  68. 'paramsSerializer': defaultToConfig2,
  69. 'timeout': defaultToConfig2,
  70. 'timeoutMessage': defaultToConfig2,
  71. 'withCredentials': defaultToConfig2,
  72. 'adapter': defaultToConfig2,
  73. 'responseType': defaultToConfig2,
  74. 'xsrfCookieName': defaultToConfig2,
  75. 'xsrfHeaderName': defaultToConfig2,
  76. 'onUploadProgress': defaultToConfig2,
  77. 'onDownloadProgress': defaultToConfig2,
  78. 'decompress': defaultToConfig2,
  79. 'maxContentLength': defaultToConfig2,
  80. 'maxBodyLength': defaultToConfig2,
  81. 'beforeRedirect': defaultToConfig2,
  82. 'transport': defaultToConfig2,
  83. 'httpAgent': defaultToConfig2,
  84. 'httpsAgent': defaultToConfig2,
  85. 'cancelToken': defaultToConfig2,
  86. 'socketPath': defaultToConfig2,
  87. 'responseEncoding': defaultToConfig2,
  88. 'validateStatus': mergeDirectKeys
  89. };
  90. // 遍历key,匹配合并策略
  91. utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) {
  92. var merge = mergeMap[prop] || mergeDeepProperties;
  93. var configValue = merge(prop);
  94. (utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);
  95. });
  96. return config;
  97. };
  1. // 合并方法,兼容多种类型
  2. function merge(/* obj1, obj2, obj3, ... */) {
  3. var result = {};
  4. function assignValue(val, key) {
  5. if (isPlainObject(result[key]) && isPlainObject(val)) {
  6. result[key] = merge(result[key], val);
  7. } else if (isPlainObject(val)) {
  8. result[key] = merge({}, val);
  9. } else if (isArray(val)) {
  10. result[key] = val.slice();
  11. } else {
  12. result[key] = val;
  13. }
  14. }
  15. for (var i = 0, l = arguments.length; i < l; i++) {
  16. forEach(arguments[i], assignValue);
  17. }
  18. return result;
  19. }
  20. // 遍历方法, 兼容array, obj
  21. function forEach(obj, fn) {
  22. // Don't bother if no value provided
  23. if (obj === null || typeof obj === 'undefined') {
  24. return;
  25. }
  26. // Force an array if not already something iterable
  27. if (typeof obj !== 'object') {
  28. /*eslint no-param-reassign:0*/
  29. obj = [obj];
  30. }
  31. if (isArray(obj)) {
  32. // Iterate over array values
  33. for (var i = 0, l = obj.length; i < l; i++) {
  34. fn.call(null, obj[i], i, obj);
  35. }
  36. } else {
  37. // Iterate over object keys
  38. for (var key in obj) {
  39. if (Object.prototype.hasOwnProperty.call(obj, key)) {
  40. fn.call(null, obj[key], key, obj);
  41. }
  42. }
  43. }
  44. }

transformer处理数据

需求

通过配置 requestTransformer统一处理请求数据
通过配置responsetransfomer统一处理响应数据

axios({
  url: '/config/post',
  method: 'post',
  headers: {
    test3: '3333',
  },
  data: {
    name: 'transform'
  },
  transformRequest: [
    function (data, headers) {
      return qs.stringify(data)
    },
    ...(axios.default.transformRequest as AxiosTransformer[]),
  ],
  transformResponse: [
    ...(axios.default.transformResponse as AxiosTransformer[]),
    function (data, headers) {
      if (typeof data === 'object') {
        data.newProp = 'test-transform-data'
      }
      return data
    }
  ]
}).then(res => {
  console.log(res)
}).catch((err: AxiosError) => {
  console.log(err)
})

添加transformer

AxiosConfig定义请求、响应的transformer,

export interface AxiosRequestConfig {
  url?: string
  method?: Method
  data?: any
  params?: any
  headers?: any
  responseType?: XMLHttpRequestResponseType
  timeout?: number
  transformRequest?: AxiosTransformer | AxiosTransformer[]
  transformResponse?: AxiosTransformer | AxiosTransformer[]
  [key: string]: any
}

export interface AxiosTransformer {
  (data: any, headers?: any): any
}

重构dispatchRequest

import { AxiosRequestConfig, AxiosResponse } from '../types/index'
import xhr from './xhr'
import { buildRUL } from '../helpers/url'
import { transformRequest, transformResponse } from '../helpers/data'
import { flattenHeaders, processHeaders } from '../helpers/headers'
import transform from './transform'

export default function dispatchRequest(config: AxiosRequestConfig) {
  return xhr(processConfig(config)).then(response => {
    return transformResponseData(response)
  })
}

// 处理请求参数
function processConfig(config: AxiosRequestConfig): AxiosRequestConfig {
  config.url = transformUrl(config)
  // processHeaders transformRequest 移到默认配置的transformRequest中了
  config.data = transform(config.data, config.headers, config.transformRequest)
  // transformHeader 为了当 datas是object时,添加content-type:application/json之后在flattenHeaders
  config.headers = flattenHeaders(config.headers, config.method!)

  return config
}
// 转换url
function transformUrl(config: AxiosRequestConfig) {
  const { url, params } = config
  return buildRUL(url || '', params)
}

// 转换response data
function transformResponseData(response: AxiosResponse) {
  return transform(response.data, response.headers, response.config.transformResponse)
}

修改默认配置

将 dispatchRequest data处理逻辑移到默认配置中

import { transformRequest, transformResponse } from "./helpers/data";
import { processHeaders } from "./helpers/headers";
import { AxiosRequestConfig } from "./types";

const defaultConfig: AxiosRequestConfig = {
  timeout: 0,
  headers: {
    common: {
      Accept: 'application/json, text/plain, */*'
    }
  },
  transformRequest: [
    function (data: any, headers: any): any {
      processHeaders(headers, data)

      return transformRequest(data)
    }
  ],
  transformResponse: [
    function (data: any): any {
      return transformResponse(data)
    }
  ]
}

export default defaultConfig

实现transform函数

import { AxiosTransformer } from "../types";

export default function transform(
  data: any,
  headers: any,
  fns?: AxiosTransformer | AxiosTransformer[]
): any {
  if (!fns) {
    return data
  }

  if (!Array.isArray(fns)) {
    fns = [fns]
  }

  fns.forEach(fn => {
    data = fn(data, headers)
  })

  return data
}

扩展axios.create静态接口

需求

目前修改default会影响所有实例, 扩展一个create方法,创建新的实例,可支持传入默认配置,从而不影响其他实例。

// 通过扩展静态接口create,创建一个新的实例
const request = axios.create({
  headers: {
    test: 'test',
  },
  transformRequest: [
    function (data, headers) {
      return qs.stringify(data)
    },
    ...(axios.default.transformRequest as AxiosTransformer[]),
  ],
  transformResponse: [
    ...(axios.default.transformResponse as AxiosTransformer[]),
    function (data, headers) {
      if (typeof data === 'object') {
        data.newProp = 'test-static-interface'
      }
      return data
    }
  ]
})

request.post('/config/post', { name: 'curry' }).then(res => {
  console.log(res)
})

定义静态接口

export interface AxiosStatic extends AxiosInstance{
  create(config?: AxiosRequestConfig): AxiosInstance
}

添加create方法

import { AxiosInstance, AxiosRequestConfig, AxiosStatic } from './types'
import Axios from './core/Axios'
import { extend } from './helpers/util'
import defaultConfig from './default'
import mergeConfig from './core/mergeConfig'

function createInstance(config: AxiosRequestConfig): AxiosStatic {
  const context = new Axios(config)
  // request function
  const instance = Axios.prototype.request.bind(context)

  // 把实例axios所有方法挂载到instance
  extend(instance, context)

  return instance as AxiosStatic
}

const axios = createInstance(defaultConfig)

axios.create = function (config?: AxiosRequestConfig) {
  return createInstance(mergeConfig(defaultConfig, config))
}

export default axios