diff --git a/src/axios.ts b/src/axios.ts index 8f8147c..d38aeac 100644 --- a/src/axios.ts +++ b/src/axios.ts @@ -2,17 +2,14 @@ * @Author: early-autumn * @Date: 2020-04-15 12:45:18 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 22:45:22 + * @LastEditTime: 2020-04-16 23:29:26 */ -import { - AxiosRequest, - AxiosRequestDefault, - AxiosMethodConfig, - ResponseData, - AxiosResponse, - AxiosInstance, -} from './types'; +import { AxiosRequestConfig, ResponseData, AxiosResponse, AxiosInstance } from './types'; import Axios from './core/Axios'; +import Cancel from './cancel/Cancel'; +import CancelToken from './cancel/CancelToken'; +import isCancel from './cancel/isCancel'; +import mergeConfig from './helper/mergeConfig'; import defaults from './helper/defaults'; /** @@ -20,7 +17,7 @@ import defaults from './helper/defaults'; * * 返回一个 axios 增强函数 */ -function createInstance(config: AxiosRequestDefault): AxiosInstance { +function createInstance(config: AxiosRequestConfig): AxiosInstance { const instance = new Axios(config); /** @@ -37,10 +34,10 @@ function createInstance(config: AxiosRequestDefault): AxiosInstance { * @param config 调用方式二: 额外配置 */ function axios( - url: AxiosRequest | string, - config: AxiosMethodConfig = {} + url: AxiosRequestConfig | string, + config: AxiosRequestConfig = {} ): Promise> { - let requestConfig: AxiosRequest; + let requestConfig: AxiosRequestConfig; // 调用方式一处理请求配置 if (typeof url !== 'string') { @@ -60,4 +57,19 @@ function createInstance(config: AxiosRequestDefault): AxiosInstance { return axios as AxiosInstance; } -export default createInstance(defaults); +const axios = createInstance(defaults); + +// 添加 Axios 类 +axios.Axios = Axios; + +// 添加 create 工厂方法 +axios.create = function create(config: AxiosRequestConfig) { + return createInstance(mergeConfig(axios.defaults, config)); +}; + +// 添加取消相关 +axios.Cancel = Cancel; +axios.CancelToken = CancelToken; +axios.isCancel = isCancel; + +export default axios; diff --git a/src/core/Axios.ts b/src/core/Axios.ts index abc8e5c..5bb9d8b 100644 --- a/src/core/Axios.ts +++ b/src/core/Axios.ts @@ -2,26 +2,16 @@ * @Author: early-autumn * @Date: 2020-04-13 18:00:27 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-16 00:11:53 + * @LastEditTime: 2020-04-17 00:10:39 */ -import { - Method, - Params, - Data, - Interceptors, - AxiosRequest, - AxiosRequestDefault, - AxiosMethodConfig, - ResponseData, - AxiosResponse, - Axios, -} from '../types'; +import { Method, Params, Data, Interceptors, AxiosRequestConfig, ResponseData, AxiosResponse, Axios } from '../types'; +import transformURL from '../helper/transformURL'; +import mergeConfig from '../helper/mergeConfig'; import InterceptorManager from './InterceptorManager'; -import mergeConfig from './mergeConfig'; import dispatchRequest from './dispatchRequest'; interface PromiseCatch { - request: Promise; + request: Promise; response?: Promise; } @@ -29,27 +19,36 @@ export default class AxiosStatic implements Axios { /** * 默认配置 */ - defaults: AxiosRequestDefault; + defaults: AxiosRequestConfig; /** * Axios 拦截器 */ public interceptors: Interceptors; - constructor(config: AxiosRequestDefault) { + constructor(config: AxiosRequestConfig) { this.defaults = config; this.interceptors = { - request: new InterceptorManager(), + request: new InterceptorManager(), response: new InterceptorManager(), }; } + /** + * baseURL + url + params 得到完整请求地址 + * + * @param config 请求配置 + */ + public getUri(config: AxiosRequestConfig): string { + return transformURL(mergeConfig(this.defaults, config)); + } + /** * 发送 HTTP 请求 * * @param config 请求配置 */ - public request(config: AxiosRequest): Promise> { + public request(config: AxiosRequestConfig): Promise> { config = mergeConfig(this.defaults, config); const promise: PromiseCatch = { @@ -57,7 +56,7 @@ export default class AxiosStatic implements Axios { }; // 执行前置拦截器 - this.interceptors.request.forEach(({ resolved, rejected }) => { + this.interceptors.request.forEach(function executor({ resolved, rejected }) { promise.request = promise.request.then(resolved, rejected); }, 'reverse'); @@ -67,7 +66,7 @@ export default class AxiosStatic implements Axios { }); // 执行后置拦截器 - this.interceptors.response.forEach(({ resolved, rejected }) => { + this.interceptors.response.forEach(function executor({ resolved, rejected }) { promise.response = promise.response?.then(resolved, rejected); }); @@ -84,7 +83,7 @@ export default class AxiosStatic implements Axios { public options( url: string, params?: Params, - config?: AxiosMethodConfig + config?: AxiosRequestConfig ): Promise> { return this._requestMethodWithoutParams('options', url, params, config); } @@ -99,7 +98,7 @@ export default class AxiosStatic implements Axios { public get( url: string, params?: Params, - config?: AxiosMethodConfig + config?: AxiosRequestConfig ): Promise> { return this._requestMethodWithoutParams('get', url, params, config); } @@ -114,7 +113,7 @@ export default class AxiosStatic implements Axios { public head( url: string, params?: Params, - config?: AxiosMethodConfig + config?: AxiosRequestConfig ): Promise> { return this._requestMethodWithoutParams('head', url, params, config); } @@ -126,7 +125,11 @@ export default class AxiosStatic implements Axios { * @param data 请求数据 * @param config 额外配置 */ - public post(url: string, data?: Data, config?: AxiosMethodConfig): Promise> { + public post( + url: string, + data?: Data, + config?: AxiosRequestConfig + ): Promise> { return this._requestMethodWithoutData('post', url, data, config); } @@ -137,7 +140,7 @@ export default class AxiosStatic implements Axios { * @param data 请求数据 * @param config 额外配置 */ - public put(url: string, data?: Data, config?: AxiosMethodConfig): Promise> { + public put(url: string, data?: Data, config?: AxiosRequestConfig): Promise> { return this._requestMethodWithoutData('put', url, data, config); } @@ -151,7 +154,7 @@ export default class AxiosStatic implements Axios { public delete( url: string, params?: Params, - config?: AxiosMethodConfig + config?: AxiosRequestConfig ): Promise> { return this._requestMethodWithoutParams('delete', url, params, config); } @@ -166,7 +169,7 @@ export default class AxiosStatic implements Axios { public trace( url: string, params?: Params, - config?: AxiosMethodConfig + config?: AxiosRequestConfig ): Promise> { return this._requestMethodWithoutParams('trace', url, params, config); } @@ -181,7 +184,7 @@ export default class AxiosStatic implements Axios { public connect( url: string, params?: Params, - config?: AxiosMethodConfig + config?: AxiosRequestConfig ): Promise> { return this._requestMethodWithoutParams('connect', url, params, config); } @@ -198,7 +201,7 @@ export default class AxiosStatic implements Axios { method: Method, url: string, params?: Params, - config: AxiosMethodConfig = {} + config: AxiosRequestConfig = {} ): Promise> { return this.request({ ...config, @@ -220,7 +223,7 @@ export default class AxiosStatic implements Axios { method: Method, url: string, data?: Data, - config: AxiosMethodConfig = {} + config: AxiosRequestConfig = {} ): Promise> { return this.request({ ...config, diff --git a/src/core/createError.ts b/src/core/createError.ts index 393f682..5ced59b 100644 --- a/src/core/createError.ts +++ b/src/core/createError.ts @@ -2,9 +2,9 @@ * @Author: early-autumn * @Date: 2020-04-14 22:23:39 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 15:50:18 + * @LastEditTime: 2020-04-16 20:17:51 */ -import { AxiosRequest, AxiosResponse } from '../types'; +import { AxiosRequestConfig, AxiosResponse } from '../types'; /** * AxiosError 继承自 Error @@ -13,19 +13,22 @@ class AxiosError extends Error { /** * 是 Axios 错误 */ - isAxiosError = true; + isAxiosError: boolean; + /** * 请求配置 */ - config: AxiosRequest; + config: AxiosRequestConfig; + /** * 请求响应体 */ response?: AxiosResponse; - constructor(message: string, config: AxiosRequest, response?: AxiosResponse) { + constructor(message: string, config: AxiosRequestConfig, response?: AxiosResponse) { super(message); + this.isAxiosError = true; this.config = config; this.response = response; @@ -43,6 +46,6 @@ class AxiosError extends Error { * @param config 请求配置 * @param response 请求响应体 */ -export default function createError(message: string, config: AxiosRequest, response?: AxiosResponse): AxiosError { +export default function createError(message: string, config: AxiosRequestConfig, response?: AxiosResponse): AxiosError { return new AxiosError(message, config, response); } diff --git a/src/core/dispatchRequest.ts b/src/core/dispatchRequest.ts index db99d01..f233648 100644 --- a/src/core/dispatchRequest.ts +++ b/src/core/dispatchRequest.ts @@ -1,36 +1,92 @@ /* * @Author: early-autumn - * @Date: 2020-04-13 15:22:22 + * @Date: 2020-04-13 18:01:16 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 20:21:13 + * @LastEditTime: 2020-04-17 00:07:35 */ -import { AxiosRequest, AxiosResponse } from '../types'; -import processURL from '../helper/processURL'; -import processData from '../helper/processData'; +import { MethodType, ResponseData, AxiosRequestConfig, AxiosResponse } from '../types'; +import { merge } from '../helper/utils'; +import transformURL from '../helper/transformURL'; +import transformData from '../helper/transformData'; +import createError from './createError'; + import request from './request'; /** - * 转换请求配置 + * 发送请求 * * @param config 请求配置 */ -function transformRequestConfig(config: AxiosRequest): void { - const { url, params, data } = config; +export default function dispatchRequest(config: AxiosRequestConfig): Promise { + return new Promise((resolve, reject) => { + const { headers = {}, cancelToken, ...options } = config; - config.url = processURL(url, params); + // 把方法转成全大写 + config.method = (config.method?.toUpperCase() ?? 'GET') as MethodType; - if (data !== undefined) { - config.data = processData(data); - } -} - -/** - * 触发请求 - * - * @param config 请求配置 - */ -export default function dispatchRequest(config: AxiosRequest): Promise { - transformRequestConfig(config); - - return request(config); + // 合并 headers + config.headers = merge( + headers.common ?? {}, + (headers[(config.method as string).toLowerCase()] ?? {}) as AnyObject, + headers + ); + + // 转换请求数据 + config.data = transformData(config.data, config.headers, config.transformRequest); + + /** + * 抛出异常 + * + * @param param0 错误信息 + * @param response 请求响应体 + */ + function catchError({ errMsg }: { errMsg: string }, response?: AxiosResponse): void { + reject(createError(errMsg, config, response)); + } + + /** + * 检查请求结果的状态码 + * + * @param result 请求结果 + */ + function checkStatusCode(result: WechatMiniprogram.RequestSuccessCallbackResult): void { + const { header: headers, ...rest } = result; + const response = { ...rest, headers, config }; + const { statusCode, errMsg } = response; + + // 成功 + if (config.validateStatus === undefined || config.validateStatus(statusCode)) { + // 转换响应数据 + response.data = transformData(response.data, response.headers, config.transformResponse) as ResponseData; + + resolve(response); + } + // 失败 + else { + // `Request failed with status code ${statusCode}` + catchError({ errMsg }, response); + } + } + + // 发送请求 + const requestTask = request({ + ...options, + url: transformURL(config), + method: config.method, + header: config.headers, + data: config.data, + success: checkStatusCode, + fail: catchError, + complete: undefined, + }); + + // 如果存在取消令牌 + // 则调用取消令牌里的 listener 监听用户的取消操作 + if (cancelToken !== undefined) { + cancelToken.listener.then(function onCanceled(reason): void { + requestTask.abort(); + reject(reason); + }); + } + }); } diff --git a/src/core/mergeConfig.ts b/src/core/mergeConfig.ts deleted file mode 100644 index 896cc7e..0000000 --- a/src/core/mergeConfig.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * @Author: early-autumn - * @Date: 2020-04-15 22:48:25 - * @LastEditors: early-autumn - * @LastEditTime: 2020-04-16 00:18:44 - */ -import { AxiosRequest, AxiosRequestDefault } from '../types'; - -/** - * 合并默认配置和请求配置 - */ -export default function mergeConfig(defaults: AxiosRequestDefault, config: AxiosRequest): AxiosRequest { - console.log(defaults); - return config; -} diff --git a/src/core/request.ts b/src/core/request.ts index 37a4891..826efc2 100644 --- a/src/core/request.ts +++ b/src/core/request.ts @@ -1,70 +1,34 @@ /* * @Author: early-autumn - * @Date: 2020-04-13 18:01:16 + * @Date: 2020-04-16 00:48:45 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 23:19:04 + * @LastEditTime: 2020-04-16 15:22:40 */ -import { MethodType, AxiosRequest, AxiosResponse } from '../types'; -import createError from './createError'; + +let request = wx?.request; /** - * 发送请求 + * #### 非微信小程序中使用 axios 前需要先调用此函数重新设置请求方法 + * + * 假设在 uniapp 中使用: + * + * ```typescript + * import axios, { setRequest } from 'axios-miniprogram'; + * + * // 先设置 request + * setRequest(uni.request); + * + * // 现在可以正常发送请求了 + * axios('/test') + * ``` + * + * * 使用 `javascript` 开发忽略, 使用 `typescript` 开发注意: `axios 类型系统`是基于`微信小程序内置类型`定义的, 在其他平台使用类型可能存在不兼容的情况 + * + * @param r 需要设置的请求方法 * - * @param config 请求配置 */ -export default function request(config: AxiosRequest): Promise { - return new Promise((resolve, reject) => { - const { cancelToken, method, ...options } = config; - // method 转为全大写 - const methodType = method?.toUpperCase() as MethodType; - - /** - * 抛出异常 - * - * @param param0 错误信息 - * @param response 请求响应体 - */ - function catchError({ errMsg }: { errMsg: string }, response?: AxiosResponse): void { - reject(createError(errMsg, config, response)); - } - - /** - * 检查请求结果的状态码 - * - * @param result 请求结果 - */ - function checkStatusCode(result: WechatMiniprogram.RequestSuccessCallbackResult): void { - const response = { ...result, config }; - const { statusCode, errMsg } = response; - - // 成功 - if (statusCode >= 200 && statusCode < 300) { - resolve(response); - } - // 失败 - else { - // `Request failed with status code ${statusCode}` - catchError({ errMsg }, response); - } - } - - // 发送请求 - // 替换 options 中的 success fail complete - const request = wx.request({ - ...options, - method: methodType, - success: checkStatusCode, - fail: catchError, - complete: undefined, - }); - - // 如果存在取消令牌 - // 则调用取消令牌里的 listener 监听用户的取消操作 - if (cancelToken !== undefined) { - cancelToken.listener.then(function onCanceled(reason): void { - request.abort(); - reject(reason); - }); - } - }); +export function setRequest(r: any): void { + request = r; } + +export default request; diff --git a/src/helper/defaults.ts b/src/helper/defaults.ts index 5825445..474ec41 100644 --- a/src/helper/defaults.ts +++ b/src/helper/defaults.ts @@ -2,13 +2,14 @@ * @Author: early-autumn * @Date: 2020-04-15 22:09:38 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 23:20:30 + * @LastEditTime: 2020-04-16 21:39:21 */ -import { AxiosRequestDefault } from '../types'; +import { AxiosRequestConfig } from '../types'; -const defaults: AxiosRequestDefault = { +const defaults: AxiosRequestConfig = { method: 'get', - header: { + timeout: 0, + headers: { common: { Accept: 'application/json, test/plain, */*', }, @@ -25,6 +26,9 @@ const defaults: AxiosRequestDefault = { trace: {}, connect: {}, }, + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; + }, }; export default defaults; diff --git a/src/helper/mergeConfig.ts b/src/helper/mergeConfig.ts new file mode 100644 index 0000000..7d12890 --- /dev/null +++ b/src/helper/mergeConfig.ts @@ -0,0 +1,62 @@ +/* + * @Author: early-autumn + * @Date: 2020-04-15 22:48:25 + * @LastEditors: early-autumn + * @LastEditTime: 2020-04-16 21:10:33 + */ +import { AxiosRequestConfig } from '../types'; +import { isPlainObject, deepMerge } from '../helper/utils'; + +/** + * 合并配置 + * + * @param config1 配置 1 + * @param config2 配置 2 + */ +export default function mergeConfig(config1: AxiosRequestConfig, config2: AxiosRequestConfig): AxiosRequestConfig { + const config: AxiosRequestConfig = {}; + + const keys1: ['url', 'method', 'data'] = ['url', 'method', 'data']; + const keys2: ['headers', 'params'] = ['headers', 'params']; + const keys3: [ + 'baseURL', + 'dataType', + 'responseType', + 'timeout', + 'enableHttp2', + 'enableQuic', + 'enableCache', + 'cancelToken' + ] = ['baseURL', 'dataType', 'responseType', 'timeout', 'enableHttp2', 'enableQuic', 'enableCache', 'cancelToken']; + + // 只取 config2 中的值 + keys1.forEach((key) => { + if (config2[key] !== undefined) { + config[key] = config2[key] as any; + } + }); + + // 深度合并 + keys2.forEach((key) => { + if (isPlainObject(config2[key])) { + config[key] = deepMerge(config1[key] as any, config2[key] as any); + } else if (config2[key] !== undefined) { + config[key] = config2[key]; + } else if (isPlainObject(config1[key])) { + config[key] = deepMerge(config1[key] as any); + } else if (config1[key] !== undefined) { + config[key] = config1[key]; + } + }); + + // 优先取 config2 中的配置 + keys3.forEach((key) => { + if (config2[key] !== undefined) { + config[key] = config2[key] as any; + } else if (config1[key] !== undefined) { + config[key] = config1[key] as any; + } + }); + + return config; +} diff --git a/src/helper/processData.ts b/src/helper/processData.ts deleted file mode 100644 index b16c5fc..0000000 --- a/src/helper/processData.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * @Author: early-autumn - * @Date: 2020-04-13 22:50:35 - * @LastEditors: early-autumn - * @LastEditTime: 2020-04-14 23:09:59 - */ -import { Data } from '../types'; -import { isPlainObject } from './utils'; - -/** - * 处理请求参数 - * - * @param data 请求参数 - */ -export default function processData(data: Data): Data { - if (!isPlainObject(data)) { - return data; - } - - return JSON.stringify(data); -} diff --git a/src/helper/processURL.ts b/src/helper/processURL.ts index 1b3aa1a..1257e9a 100644 --- a/src/helper/processURL.ts +++ b/src/helper/processURL.ts @@ -2,7 +2,7 @@ * @Author: early-autumn * @Date: 2020-04-13 21:45:45 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 11:54:36 + * @LastEditTime: 2020-04-16 21:55:26 */ import { Params } from '../types'; import { isPlainObject, isDate } from './utils'; @@ -24,36 +24,35 @@ function encode(str: string): string { } /** - * 拼接 URL 和 参数 + * 拼接 URL 和 序列化参数 * - * @param url 请求地址 - * @param paramsStr 请求参数 + * @param url 请求地址 + * @param serializedParams 序列化参数 */ -function joinURL(url: string, paramsStr: string): string { +function joinURL(url: string, serializedParams: string): string { + if (serializedParams === '') { + return url; + } + // 移除 hash - const hashIndex = paramsStr.indexOf('#'); + const hashIndex = serializedParams.indexOf('#'); if (hashIndex !== -1) { - paramsStr = paramsStr.slice(0, hashIndex); + serializedParams = serializedParams.slice(0, hashIndex); } // 拼接前缀 const prefix = url.indexOf('?') === -1 ? '?' : '&'; - paramsStr = `${prefix}${paramsStr}`; + serializedParams = `${prefix}${serializedParams}`; - return `${url}${paramsStr}`; + return `${url}${serializedParams}`; } /** - * 处理 URL 参数 + * 默认参数序列化 * - * @param url 请求地址 * @param params 请求参数 */ -export default function processURL(url: string, params?: Params): string { - if (params === undefined) { - return url; - } - +function paramsSerializerDefault(params: AnyObject): string { const parts: string[] = []; Object.entries(params).forEach(([key, value]): void => { @@ -80,9 +79,32 @@ export default function processURL(url: string, params?: Params): string { }); }); - if (parts.length !== 0) { - url = joinURL(url, parts.join('&')); + return parts.join('&'); +} + +/** + * 处理 URL 参数 + * + * @param url 请求地址 + * @param params 请求参数 + * @param paramsSerializer 自定义参数序列化 + */ +export default function processURL( + url: string, + params?: Params, + paramsSerializer?: (params: AnyObject) => string +): string { + if (params === undefined) { + return url; } - return url; + let serializedParams = ''; + + if (paramsSerializer !== undefined) { + serializedParams = paramsSerializer(params); + } else { + serializedParams = paramsSerializerDefault(params); + } + + return joinURL(url, serializedParams); } diff --git a/src/helper/transformData.ts b/src/helper/transformData.ts new file mode 100644 index 0000000..dec289e --- /dev/null +++ b/src/helper/transformData.ts @@ -0,0 +1,30 @@ +/* + * @Author: early-autumn + * @Date: 2020-04-16 22:37:44 + * @LastEditors: early-autumn + * @LastEditTime: 2020-04-17 00:09:19 + */ +import { Data, Headers, TransformData } from '../types'; + +/** + * 转换数据 + * + * @param data 请求数据或响应诗句 + * @param headers 请求头或响应头 + * @param transforms 请求数据转换函数或响应数据转换函数 + */ +export default function transformData(data: Data, headers?: Headers, transforms?: TransformData | TransformData[]) { + if (transforms === undefined) { + return data; + } + + if (!Array.isArray(transforms)) { + transforms = [transforms]; + } + + transforms.forEach((transform: TransformData) => { + data = transform(data, headers); + }); + + return data; +} diff --git a/src/helper/transformURL.ts b/src/helper/transformURL.ts new file mode 100644 index 0000000..f4edc62 --- /dev/null +++ b/src/helper/transformURL.ts @@ -0,0 +1,21 @@ +/* + * @Author: early-autumn + * @Date: 2020-04-17 00:00:21 + * @LastEditors: early-autumn + * @LastEditTime: 2020-04-17 00:09:47 + */ +import { AxiosRequestConfig } from '../types'; +import { isAbsoluteURL, combineURL } from './utils'; +import processURL from './processURL'; + +/** + * baseURL + url + params 得到完整请求地址 + * + * @param config 请求配置 + */ +export default function transformURL(config: AxiosRequestConfig): string { + const { baseURL = '', url = '', params, paramsSerializer } = config; + const fullURL = isAbsoluteURL(url) ? url : combineURL(baseURL, url); + + return processURL(fullURL, params, paramsSerializer); +} diff --git a/src/helper/utils.ts b/src/helper/utils.ts index a950cf8..fa4e568 100644 --- a/src/helper/utils.ts +++ b/src/helper/utils.ts @@ -2,7 +2,7 @@ * @Author: early-autumn * @Date: 2020-04-13 21:55:40 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-14 14:28:31 + * @LastEditTime: 2020-04-16 23:37:50 */ const _toString = Object.prototype.toString; @@ -24,6 +24,78 @@ export function isPlainObject(obj: any): obj is object { return _toString.call(obj) === '[object Object]'; } -// export function isObject(value: any): value is object { -// return value !== null && typeof value === 'object'; -// } +/** + * 检查是否是一个绝对 URL + * + * xxx:// 或者 "//" 开头, 视为绝对地址 + * + * @param url 需要检查的 URL + */ +export function isAbsoluteURL(url: string): boolean { + return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); +} + +/** + * 拼接 baseURL 和 url 获得完整的 URL + * + * combineURL('1/2///','////3/4') => '1/2/3/4' + */ +export function combineURL(baseURL: string, url: string): string { + return `${baseURL.replace(/\/*$/, '')}/${url.replace(/^\/*/, '')}`; +} + +/** + * 浅合并多个对象 + * + * @param objs n 个对象 + */ +export function merge(...objs: Record[]): Record { + const result: Record = {}; + + function assignValue(key: string, val: any): void { + if (isPlainObject(result[key]) && isPlainObject(val)) { + result[key] = merge(result[key], val); + } else { + result[key] = val; + } + } + + objs.forEach((obj: Record): void => { + Object.entries(obj).forEach(([key, value]) => assignValue(key, value)); + }); + + return result; +} + +/** + * 深度合并多个对象 + * + * @param objs n 个对象 + */ +export function deepMerge(...objs: Record[]): Record { + const result: Record = {}; + + function assignValue(key: string, val: any) { + // 如果当前结果和当前值都为普通对象 + // 递归进行深度合并 + if (isPlainObject(result[key]) && isPlainObject(val)) { + result[key] = deepMerge(result[key], val); + } + // 如果只有当前值为普通对象 + // 直接深拷贝当前值 + else if (isPlainObject(val)) { + result[key] = deepMerge({}, val); + } + // 如果都不是普通对象 + // 直接赋值 + else { + result[key] = val; + } + } + + objs.forEach((obj: Record): void => { + Object.entries(obj).forEach(([key, value]) => assignValue(key, value)); + }); + + return result; +} diff --git a/src/index.ts b/src/index.ts index 1c2412d..c4178f4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,34 +2,11 @@ * @Author: early-autumn * @Date: 2020-04-14 23:22:52 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 16:56:41 + * @LastEditTime: 2020-04-16 10:20:12 */ import axios from './axios'; +import { setRequest } from './core/request'; -interface Test { - test1: string; - test2: string; - test3: string; -} +export { setRequest }; -axios('/test').then((res) => { - console.log(res.data.test3); -}); - -axios({ url: '' }).then((res) => { - console.log(res.data.test1); -}); - -axios - .request({ url: '' }) - .then((res) => { - console.log(res.data.test1); - }); - -axios.get('', {}, {}).then((res) => { - console.log(res.data.test1); -}); - -// axios.post('', {}, {}).then((res) => { -// console.log(res.data); -// }); +export default axios; diff --git a/src/types.ts b/src/types.ts index e837be3..d51621e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,7 @@ * @Author: early-autumn * @Date: 2020-04-13 15:23:53 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-15 23:48:48 + * @LastEditTime: 2020-04-17 00:06:34 */ import 'miniprogram-api-typings'; @@ -31,10 +31,80 @@ export declare type Params = AnyObject; */ export declare type Data = WechatMiniprogram.RequestOption['data']; +/** + * Axios 请求头 + */ +export interface Headers { + /** + * 通用配置 + */ + common?: Record; + + /** + * options 配置 + */ + options?: Record; + + /** + * get 配置 + */ + get?: Record; + + /** + * head 配置 + */ + head?: Record; + + /** + * post 配置 + */ + post?: Record; + + /** + * put 配置 + */ + put?: Record; + + /** + * delete 配置 + */ + delete?: Record; + + /** + * trace 配置 + */ + trace?: Record; + + /** + * connect 配置 + */ + connect?: Record; + + /** + * 自定义配置 + */ + [x: string]: string | Record | undefined; +} + +export interface TransformData { + (data: Data, headers?: Headers): Data; +} + /** * 请求配置 */ -export type AxiosRequest = Omit & { +export declare interface AxiosRequestConfig + extends Pick { + /** + * 基础地址 + */ + baseURL?: string; + + /** + * 开发者服务器接口地址 + */ + url?: string; + /** HTTP 请求方法 * * 可选值: @@ -67,6 +137,21 @@ export type AxiosRequest = Omit & { /** - * 基础地址 + * 自定义合法状态码 */ - baseURL?: string; + validateStatus?: (status: number) => boolean; + /** - * 请求头 + * 自定义参数序列化 */ - header?: { - /** - * 公共请求头 - */ - common?: AnyObject; - /** - * options 请求头 - */ - options?: AnyObject; - /** - * get 请求头 - */ - get?: AnyObject; - /** - * head 请求头 - */ - head?: AnyObject; - /** - * post 请求头 - */ - post?: AnyObject; - /** - * put 请求头 - */ - put?: AnyObject; - /** - * delete 请求头 - */ - delete?: AnyObject; - /** - * trace 请求头 - */ - trace?: AnyObject; - /** - * connect 请求头 - */ - connect?: AnyObject; - }; -}; + paramsSerializer?: (params: AnyObject) => string; +} /** * 服务端响应数据 @@ -148,16 +192,21 @@ export declare type ResponseData = WechatMiniprogram.RequestSuccessCallbackResul * 请求响应体 */ export interface AxiosResponse - extends WechatMiniprogram.RequestSuccessCallbackResult { + extends Omit { /** * 开发者服务器返回的数据 */ data: T; + /** + * 开发者服务器返回的 HTTP Response Headers + */ + headers: Headers; + /** * 请求配置 */ - config: AxiosRequest; + config: AxiosRequestConfig; } /** @@ -177,7 +226,7 @@ export interface InterceptorRejected { /** * 拦截器 */ -export declare type Interceptor = { +export interface Interceptor { /** * 拦截器成功的回调函数 */ @@ -186,7 +235,7 @@ export declare type Interceptor = { * 拦截器失败的回调函数 */ rejected: InterceptorRejected; -}; +} /** * 拦截器执行器 @@ -230,19 +279,13 @@ export interface Interceptors { /** * request 请求前置拦截器 */ - request: InterceptorManager; + request: InterceptorManager; /** * response 请求后置拦截器 */ response: InterceptorManager; } - -/** - * Axios 请求方法别名额外配置类型 - */ -export type AxiosMethodConfig = Omit; - /** * Axios */ @@ -250,19 +293,26 @@ export interface Axios { /** * 默认配置 */ - defaults: AxiosRequestDefault; + defaults: AxiosRequestConfig; /** * Axios 拦截器 */ interceptors: Interceptors; + /** + * baseURL + url + params 得到完整请求地址 + * + * @param config 请求配置 + */ + getUri(config: AxiosRequestConfig): string; + /** * 发送 HTTP 请求 * * @param config 请求配置 */ - request(config: AxiosRequest): Promise>; + request(config: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 OPTIONS @@ -271,7 +321,7 @@ export interface Axios { * @param params 请求参数 * @param config 额外配置 */ - options(url: string, params?: Params, config?: AxiosMethodConfig): Promise>; + options(url: string, params?: Params, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 GET @@ -280,7 +330,7 @@ export interface Axios { * @param params 请求参数 * @param config 额外配置 */ - get(url: string, params?: Params, config?: AxiosMethodConfig): Promise>; + get(url: string, params?: Params, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 HEAD @@ -289,7 +339,7 @@ export interface Axios { * @param params 请求参数 * @param config 额外配置 */ - head(url: string, params?: Params, config?: AxiosMethodConfig): Promise>; + head(url: string, params?: Params, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 POST @@ -298,7 +348,7 @@ export interface Axios { * @param data 请求数据 * @param config 额外配置 */ - post(url: string, data?: Data, config?: AxiosMethodConfig): Promise>; + post(url: string, data?: Data, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 PUT @@ -307,7 +357,7 @@ export interface Axios { * @param data 请求数据 * @param config 额外配置 */ - put(url: string, data?: Data, config?: AxiosMethodConfig): Promise>; + put(url: string, data?: Data, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 DELETE @@ -316,7 +366,7 @@ export interface Axios { * @param params 请求参数 * @param config 额外配置 */ - delete(url: string, params?: Params, config?: AxiosMethodConfig): Promise>; + delete(url: string, params?: Params, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 TRACE @@ -325,7 +375,7 @@ export interface Axios { * @param params 请求参数 * @param config 额外配置 */ - trace(url: string, params?: Data, config?: AxiosMethodConfig): Promise>; + trace(url: string, params?: Data, config?: AxiosRequestConfig): Promise>; /** * 发送 HTTP 请求 CONNECT @@ -334,29 +384,14 @@ export interface Axios { * @param params 请求参数 * @param config 额外配置 */ - connect(url: string, params?: Data, config?: AxiosMethodConfig): Promise>; + connect(url: string, params?: Data, config?: AxiosRequestConfig): Promise>; } /** - * axios 增强函数 - * - * 支持两种调用方式, 并含有 Axios 实例的所有属性和方法 + * Axios 类接口 */ -export interface AxiosInstance extends Axios { - /** - * 调用方式一 - * - * @param config 请求配置 - */ - (config: AxiosRequest): Promise>; - - /** - * 调用方式二 - * - * @param url 请求地址 - * @param config 额外配置 - */ - (url: string, config?: AxiosMethodConfig): Promise>; +export interface AxiosConstructor { + new (config: AxiosRequestConfig): Axios; } /** @@ -371,7 +406,7 @@ export interface AxiosError extends Error { /** * 请求配置 */ - config: AxiosRequest; + config: AxiosRequestConfig; /** * 请求响应体 @@ -428,6 +463,26 @@ export interface CancelToken { throwIfRequested(): void; } +/** + * 取消令牌类接口 + */ + +export interface CancelTokenConstructor { + new (executor: CancelExecutor): CancelToken; + /** + * 返回一个 CancelTokenSource + * + * CancelTokenSource.token 是一个 CancelToken 对象 + * + * CancelTokenSource.cancel 是一个 CancelAction 函数 + * + * 调用 CancelTokenSource.cancel('这里可以填写您的错误信息') + * + * 取消 CancelTokenSource.token + */ + source(): CancelTokenSource; +} + /** * 取消令牌 source */ @@ -442,3 +497,54 @@ export interface CancelTokenSource { */ cancel: CancelAction; } + +/** + * axios 增强函数 + * + * 支持两种调用方式, 并含有 Axios 实例的所有属性和方法 + */ +export interface AxiosInstance extends Axios { + /** + * 调用方式一 + * + * @param config 请求配置 + */ + (config: AxiosRequestConfig): Promise>; + + /** + * 调用方式二 + * + * @param url 请求地址 + * @param config 额外配置 + */ + (url: string, config?: AxiosRequestConfig): Promise>; + + /** + * Axios 类 + */ + Axios: AxiosConstructor; + + /** + * 创建 Axios 实例 + * + * @param config 全局配置 + */ + create(config: AxiosRequestConfig): Axios; + + /** + * Cancel 类 + */ + Cancel: Cancel; + + /** + * CancelToken 类 + */ + CancelToken: CancelTokenConstructor; + + /** + * 是不是一个取消对象 + * + * @param value 判断的值 + */ + isCancel: (value: any) => boolean; +} diff --git a/test/index.test.ts b/test/index.test.ts index b5221a1..f65bed2 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -2,12 +2,12 @@ * @Author: early-autumn * @Date: 2020-04-14 23:43:45 * @LastEditors: early-autumn - * @LastEditTime: 2020-04-14 23:45:11 + * @LastEditTime: 2020-04-17 00:14:34 */ import { isDate } from '../src/helper/utils'; describe('', () => { - it('', () => { + it('?', () => { expect(isDate({})).toBeFalsy(); }); });