合并配置
需求分析
支持默认配置和传入配置合并
axios.default.timeout = 2000axios.default.headers.common.test1 = 111axios.default.headers.post.test2 = 2222axios({url: '/config/post',method: 'post',headers: {test3: '3333',},data: qs.stringify({name: '1'})})
默认配置定义
import { AxiosRequestConfig } from "./types";const defaultConfig: AxiosRequestConfig = {timeout: 0,headers: {common: {Accept: 'application/json, text/plain, */*'}}}const methodsWithNoData = ['get', 'delete', 'options', 'head']methodsWithNoData.forEach(method => [defaultConfig.headers[method] = {}])const methodsWithData = ['post', 'patch', 'put']methodsWithData.forEach(method => [defaultConfig.headers[method] = {'Content-Type': 'application/x-www-form-urlencoded'}])export default defaultConfig
添加axios属性
export default class Axios {default: AxiosRequestConfigconstructor(initConfig: AxiosRequestConfig) {this.default = initConfig}}
合并策略
- 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 }
定义deepMerge函数```typescript/* obj1,obj2, obj3... */export function deepMerge(...objs: any[]): any {const result = Object.create(null)objs.forEach(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}
在Axios.request里调用, 合并配置
config = mergeConfig(this.default, config)
取出headers数据
调用 flattenHeaders
function processConfig(config: AxiosRequestConfig): AxiosRequestConfig {config.url = transformUrl(config)// 在处理data之前处理headersconfig.headers = transformHeader(config)config.data = transformRequestData(config)// transformHeader 为了当 datas是object时,添加content-type:application/json之后在flattenHeadersconfig.data = flattenHeaders(config.headers, config.method!)return config}
export function flattenHeaders(headers: any, method: string): any {if (!headers) {return headers}headers = deepMerge(headers.common, headers[method], headers)const methods = ['common', 'get', 'delete', 'options', 'head', 'post', 'put', 'patch']methods.forEach(method => {delete headers[method]})return headers}
官方实现方法
'use strict';var utils = require('../utils');/*** Config-specific merge-function which creates a new config-object* by merging two configuration objects together.** @param {Object} config1* @param {Object} config2** @returns {Object} New object resulting from merging config2 to config1*/module.exports = function mergeConfig(config1, config2) {// eslint-disable-next-line no-param-reassignconfig2 = config2 || {};var config = {};// 值合并方法 把targe合并进source,返回sourcefunction getMergedValue(target, source) {if (utils.isPlainObject(target) && utils.isPlainObject(source)) {return utils.merge(target, source);} else if (utils.isPlainObject(source)) {return utils.merge({}, source);} else if (utils.isArray(source)) {return source.slice();}return source;}// 默认合并策略function mergeDeepProperties(prop) {if (!utils.isUndefined(config2[prop])) {// config2 有值return getMergedValue(config1[prop], config2[prop]);} else if (!utils.isUndefined(config1[prop])) {// config1 有值, config2 无值return getMergedValue(undefined, config1[prop]);}}// 只取config2, 如: url, data,methodfunction valueFromConfig2(prop) {if (!utils.isUndefined(config2[prop])) {return getMergedValue(undefined, config2[prop]);}}// 【互斥取值】,优先config2// 如:timeout, baseURL...function defaultToConfig2(prop) {if (!utils.isUndefined(config2[prop])) {return getMergedValue(undefined, config2[prop]);} else if (!utils.isUndefined(config1[prop])) {return getMergedValue(undefined, config1[prop]);}}// 【互斥取值】,优先config2,和defaultToConfig2不同的是,这里即使传入空,也会覆盖function mergeDirectKeys(prop) {if (prop in config2) {return getMergedValue(config1[prop], config2[prop]);} else if (prop in config1) {return getMergedValue(undefined, config1[prop]);}}// 合并策略var mergeMap = {'url': valueFromConfig2,'method': valueFromConfig2,'data': valueFromConfig2,'baseURL': defaultToConfig2,'transformRequest': defaultToConfig2,'transformResponse': defaultToConfig2,'paramsSerializer': defaultToConfig2,'timeout': defaultToConfig2,'timeoutMessage': defaultToConfig2,'withCredentials': defaultToConfig2,'adapter': defaultToConfig2,'responseType': defaultToConfig2,'xsrfCookieName': defaultToConfig2,'xsrfHeaderName': defaultToConfig2,'onUploadProgress': defaultToConfig2,'onDownloadProgress': defaultToConfig2,'decompress': defaultToConfig2,'maxContentLength': defaultToConfig2,'maxBodyLength': defaultToConfig2,'beforeRedirect': defaultToConfig2,'transport': defaultToConfig2,'httpAgent': defaultToConfig2,'httpsAgent': defaultToConfig2,'cancelToken': defaultToConfig2,'socketPath': defaultToConfig2,'responseEncoding': defaultToConfig2,'validateStatus': mergeDirectKeys};// 遍历key,匹配合并策略utils.forEach(Object.keys(config1).concat(Object.keys(config2)), function computeConfigValue(prop) {var merge = mergeMap[prop] || mergeDeepProperties;var configValue = merge(prop);(utils.isUndefined(configValue) && merge !== mergeDirectKeys) || (config[prop] = configValue);});return config;};
// 合并方法,兼容多种类型function merge(/* obj1, obj2, obj3, ... */) {var result = {};function assignValue(val, key) {if (isPlainObject(result[key]) && isPlainObject(val)) {result[key] = merge(result[key], val);} else if (isPlainObject(val)) {result[key] = merge({}, val);} else if (isArray(val)) {result[key] = val.slice();} else {result[key] = val;}}for (var i = 0, l = arguments.length; i < l; i++) {forEach(arguments[i], assignValue);}return result;}// 遍历方法, 兼容array, objfunction forEach(obj, fn) {// Don't bother if no value providedif (obj === null || typeof obj === 'undefined') {return;}// Force an array if not already something iterableif (typeof obj !== 'object') {/*eslint no-param-reassign:0*/obj = [obj];}if (isArray(obj)) {// Iterate over array valuesfor (var i = 0, l = obj.length; i < l; i++) {fn.call(null, obj[i], i, obj);}} else {// Iterate over object keysfor (var key in obj) {if (Object.prototype.hasOwnProperty.call(obj, key)) {fn.call(null, obj[key], key, obj);}}}}
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
