feat: 添加新功能 派生领域 axios.fork()

pull/41/head
zjx0905 2023-04-06 22:59:49 +08:00
parent 43c18d1723
commit 222b935f68
7 changed files with 345 additions and 163 deletions

View File

@ -1,4 +1,6 @@
import { buildURL } from '../helpers/buildURL'; import { buildURL } from '../helpers/buildURL';
import { isAbsoluteURL } from '../helpers/isAbsoluteURL';
import { combineURL } from '../helpers/combineURL';
import { mergeConfig } from './mergeConfig'; import { mergeConfig } from './mergeConfig';
import { import {
AxiosAdapter, AxiosAdapter,
@ -12,6 +14,7 @@ import { CancelToken } from './cancel';
import dispatchRequest from './dispatchRequest'; import dispatchRequest from './dispatchRequest';
import InterceptorManager from './InterceptorManager'; import InterceptorManager from './InterceptorManager';
import { AxiosTransformer } from './transformData'; import { AxiosTransformer } from './transformData';
import AxiosDomain from './AxiosDomain';
export type AxiosRequestMethod = export type AxiosRequestMethod =
| AxiosAdapterRequestMethod | AxiosAdapterRequestMethod
@ -188,72 +191,7 @@ export interface AxiosConstructor {
new (config: AxiosRequestConfig): Axios; new (config: AxiosRequestConfig): Axios;
} }
export interface AxiosAliasMethod { export default class Axios extends AxiosDomain {
<TData = unknown>(
/**
*
*/
url: string,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
export interface AxiosWithParamsAliasMethod {
<TData = unknown>(
/**
*
*/
url: string,
/**
*
*/
params?: AnyObject,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
export interface AxiosWithDataAliasMethod {
<TData = unknown>(
/**
*
*/
url: string,
/**
*
*/
data?: AnyObject,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
export default class Axios {
/**
*
*/
static as = ['options', 'trace', 'connect'] as const;
/**
*
*/
static pas = ['head', 'get', 'delete'] as const;
/**
*
*/
static das = ['post', 'put'] as const;
/**
*
*/
defaults: AxiosRequestConfig;
/** /**
* *
*/ */
@ -268,85 +206,8 @@ export default class Axios {
response: new InterceptorManager<AxiosResponse>(), response: new InterceptorManager<AxiosResponse>(),
}; };
/**
* options
*/
options!: AxiosAliasMethod;
/**
* get
*/
get!: AxiosWithParamsAliasMethod;
/**
* head
*/
head!: AxiosWithParamsAliasMethod;
/**
* post
*/
post!: AxiosWithDataAliasMethod;
/**
* put
*/
put!: AxiosWithDataAliasMethod;
/**
* delete
*/
delete!: AxiosWithParamsAliasMethod;
/**
* trace
*/
trace!: AxiosAliasMethod;
/**
* connect
*/
connect!: AxiosAliasMethod;
/**
*
*
* @param defaults
*/
constructor(defaults: AxiosRequestConfig = {}) { constructor(defaults: AxiosRequestConfig = {}) {
this.defaults = defaults; super(defaults, (...args) => this.#processRequest(...args));
for (const alias of Axios.as) {
this[alias] = (url, config = {}) => {
return this.request({
...config,
method: alias,
url,
});
};
}
for (const alias of Axios.pas) {
this[alias] = (url, params, config = {}) => {
return this.request({
...config,
method: alias,
params,
url,
});
};
}
for (const alias of Axios.das) {
this[alias] = (url, data, config = {}) => {
return this.request({
...config,
method: alias,
data,
url,
});
};
}
} }
getUri(config: AxiosRequestConfig): string { getUri(config: AxiosRequestConfig): string {
@ -354,22 +215,25 @@ export default class Axios {
this.defaults, this.defaults,
config, config,
); );
return buildURL(url, params, paramsSerializer).replace(/^\?/, ''); return buildURL(url, params, paramsSerializer).replace(/^\?/, '');
} }
/** /**
* *
*
* @param config
*/ */
request<TData = unknown>( fork(defaults: AxiosRequestConfig) {
config: AxiosRequestConfig, const config = mergeConfig(this.defaults, defaults);
): Promise<AxiosResponse<TData>> { const { baseURL = '' } = defaults;
const requestConfig = mergeConfig(this.defaults, config); if (!isAbsoluteURL(baseURL)) {
config.baseURL = combineURL(this.defaults.baseURL ?? '', baseURL);
}
return new AxiosDomain(config, this.#processRequest);
}
#processRequest = <TData = unknown>(config: AxiosRequestConfig) => {
const { request, response } = this.interceptors; const { request, response } = this.interceptors;
let promiseRequest = Promise.resolve(requestConfig); let promiseRequest = Promise.resolve(config);
request.forEach(({ resolved, rejected }) => { request.forEach(({ resolved, rejected }) => {
promiseRequest = promiseRequest.then( promiseRequest = promiseRequest.then(
resolved, resolved,
@ -382,7 +246,6 @@ export default class Axios {
AxiosResponse<unknown> AxiosResponse<unknown>
>; >;
}); });
return promiseResponse as Promise<AxiosResponse<TData>>; return promiseResponse as Promise<AxiosResponse<TData>>;
} };
} }

221
src/core/AxiosDomain.ts Normal file
View File

@ -0,0 +1,221 @@
import { isPlainObject, isString } from '../helpers/isTypes';
import { AxiosRequestConfig, AxiosRequestData, AxiosResponse } from './Axios';
import { mergeConfig } from './mergeConfig';
export interface AxiosDomainAsRequest {
<TData = unknown>(
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
<TData = unknown>(
/**
*
*/
url: string,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
export interface AxiosDomainAsRequestWithParams {
<TData = unknown>(
/**
*
*/
params?: AnyObject,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
<TData = unknown>(
/**
*
*/
url: string,
/**
*
*/
params?: AnyObject,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
export interface AxiosDomainAsRequestWithData {
<TData = unknown>(
/**
*
*/
data?: AnyObject,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
<TData = unknown>(
/**
*
*/
url: string,
/**
*
*/
data?: AnyObject,
/**
*
*/
config?: AxiosRequestConfig,
): Promise<AxiosResponse<TData>>;
}
export interface AxiosDomainRequest {
<TData = unknown>(config: AxiosRequestConfig): Promise<AxiosResponse<TData>>;
}
export default class AxiosDomain {
/**
*
*/
static as = ['options', 'trace', 'connect'] as const;
/**
*
*/
static asp = ['head', 'get', 'delete'] as const;
/**
*
*/
static asd = ['post', 'put'] as const;
/**
*
*/
defaults: AxiosRequestConfig;
/**
*
*/
request!: AxiosDomainRequest;
/**
* options
*/
options!: AxiosDomainAsRequest;
/**
* get
*/
get!: AxiosDomainAsRequestWithParams;
/**
* head
*/
head!: AxiosDomainAsRequestWithParams;
/**
* post
*/
post!: AxiosDomainAsRequestWithData;
/**
* put
*/
put!: AxiosDomainAsRequestWithData;
/**
* delete
*/
delete!: AxiosDomainAsRequestWithParams;
/**
* trace
*/
trace!: AxiosDomainAsRequest;
/**
* connect
*/
connect!: AxiosDomainAsRequest;
constructor(
defaults: AxiosRequestConfig = {},
processRequest: AxiosDomainRequest,
) {
this.defaults = defaults;
this.request = <TData = unknown>(config: AxiosRequestConfig) => {
return processRequest<TData>(mergeConfig(this.defaults, config));
};
this.#createAsRequests();
this.#createAspRequests();
this.#createAsdRequests();
}
#createAsRequests() {
for (const alias of AxiosDomain.as) {
this[alias] = function processAsRequest(
urlOrConfig?: string | AxiosRequestConfig,
config: AxiosRequestConfig = {},
) {
if (isString(urlOrConfig)) {
config.url = urlOrConfig;
} else if (isPlainObject(urlOrConfig)) {
config = urlOrConfig;
}
config.method = alias;
return this.request(config);
};
}
}
#createAspRequests() {
for (const alias of AxiosDomain.asp) {
this[alias] = function processAspRequest(
urlOrParams?: string | AxiosRequestConfig,
paramsOrConfig: AxiosRequestConfig | AnyObject = {},
config: AxiosRequestConfig = {},
) {
if (isString(urlOrParams)) {
config.url = urlOrParams;
config.params = paramsOrConfig;
} else if (isPlainObject(urlOrParams)) {
config = paramsOrConfig;
config.params = urlOrParams;
}
config.method = alias;
return this.request(config);
};
}
}
#createAsdRequests() {
for (const alias of AxiosDomain.asd) {
this[alias] = function processAsdRequest(
urlOrData?: string | AxiosRequestConfig,
dataOrConfig: AxiosRequestData | AxiosRequestConfig = {},
config: AxiosRequestConfig = {},
) {
if (isString(urlOrData)) {
config.url = urlOrData;
config.data = dataOrConfig;
} else if (isPlainObject(urlOrData)) {
config = dataOrConfig;
config.data = urlOrData;
}
config.method = alias;
return this.request(config);
};
}
}
}

View File

@ -33,8 +33,9 @@ describe('src/axios.ts', () => {
expect(axios.request).toBeTypeOf('function'); expect(axios.request).toBeTypeOf('function');
expect(axios.getUri).toBeTypeOf('function'); expect(axios.getUri).toBeTypeOf('function');
expect(axios.fork).toBeTypeOf('function');
[...Axios.as, ...Axios.pas, ...Axios.das].forEach((k) => { [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => {
expect(axios[k]).toBeTypeOf('function'); expect(axios[k]).toBeTypeOf('function');
}); });
}); });
@ -77,7 +78,7 @@ describe('src/axios.ts', () => {
}), }),
}; };
Axios.pas.forEach((a) => { Axios.asp.forEach((a) => {
axios[a]('test', p, c1).then((res) => { axios[a]('test', p, c1).then((res) => {
expect(res.data).toEqual(data); expect(res.data).toEqual(data);
}); });
@ -108,7 +109,7 @@ describe('src/axios.ts', () => {
}), }),
}; };
Axios.das.forEach((a) => { Axios.asd.forEach((a) => {
axios[a]('test', d, c1).then((res) => { axios[a]('test', d, c1).then((res) => {
expect(res.data).toEqual(data); expect(res.data).toEqual(data);
}); });

View File

@ -4,8 +4,8 @@ import Axios from 'src/core/Axios';
describe('src/core/Axios.ts', () => { describe('src/core/Axios.ts', () => {
test('应该有这些静态属性', () => { test('应该有这些静态属性', () => {
expect(Axios.as).toEqual(['options', 'trace', 'connect']); expect(Axios.as).toEqual(['options', 'trace', 'connect']);
expect(Axios.pas).toEqual(['head', 'get', 'delete']); expect(Axios.asp).toEqual(['head', 'get', 'delete']);
expect(Axios.das).toEqual(['post', 'put']); expect(Axios.asd).toEqual(['post', 'put']);
}); });
test('应该有这些实例属性', () => { test('应该有这些实例属性', () => {
@ -18,8 +18,9 @@ describe('src/core/Axios.ts', () => {
expect(a.interceptors).toBeTypeOf('object'); expect(a.interceptors).toBeTypeOf('object');
expect(a.request).toBeTypeOf('function'); expect(a.request).toBeTypeOf('function');
expect(a.getUri).toBeTypeOf('function'); expect(a.getUri).toBeTypeOf('function');
expect(a.fork).toBeTypeOf('function');
[...Axios.as, ...Axios.pas, ...Axios.das].forEach((k) => { [...Axios.as, ...Axios.asp, ...Axios.asd].forEach((k) => {
expect(a[k]).toBeTypeOf('function'); expect(a[k]).toBeTypeOf('function');
}); });
}); });

View File

@ -0,0 +1,96 @@
import { describe, test, expect, vi } from 'vitest';
import AxiosDomain, { AxiosDomainRequest } from 'src/core/AxiosDomain';
import { ignore } from 'src/helpers/ignore';
describe('src/core/Axios.ts', () => {
test('应该有这些静态属性', () => {
expect(AxiosDomain.as).toEqual(['options', 'trace', 'connect']);
expect(AxiosDomain.asp).toEqual(['head', 'get', 'delete']);
expect(AxiosDomain.asd).toEqual(['post', 'put']);
});
test('应该有这些实例属性', () => {
const c = {
baseURL: 'http://api.com',
};
const a = new AxiosDomain(c, vi.fn());
expect(a.defaults).toEqual(c);
expect(a.request).toBeTypeOf('function');
[...AxiosDomain.as, ...AxiosDomain.asp, ...AxiosDomain.asd].forEach((k) => {
expect(a[k]).toBeTypeOf('function');
});
});
test('发送请求时 processRequest 应该被调用', () => {
const p = vi.fn();
const d = {
baseURL: 'http://api.com',
};
const c = {
url: 'test',
params: {
id: 1,
},
data: {
id: 1,
},
};
new AxiosDomain(d, p).request(c);
expect(p).toBeCalled();
expect(p.mock.calls[0][0]).toEqual({
...d,
...c,
});
});
test('应该可以调用这些方法', () => {
const cb = vi.fn();
const d = {
baseURL: 'http://api.com',
};
const c = {
url: 'test',
params: {
id: 1,
},
data: {
id: 1,
},
};
const a = new AxiosDomain(d, ((config) => {
cb();
expect(config.baseURL).toBe(d.baseURL);
expect(config.url).toBe(c.url);
expect(config.params).toEqual(c.params);
expect(config.data).toEqual(c.data);
}) as AxiosDomainRequest);
a.request(c);
AxiosDomain.as.forEach((k) => a[k](c));
AxiosDomain.as.forEach((k) => a[k](c.url, ignore(c, 'url')));
AxiosDomain.asp.forEach((k) => a[k](c.params, ignore(c, 'params')));
AxiosDomain.asp.forEach((k) =>
a[k](c.url, c.params, ignore(c, 'url', 'params')),
);
AxiosDomain.asd.forEach((k) => a[k](c.data, ignore(c, 'data')));
AxiosDomain.asd.forEach((k) =>
a[k](c.url, c.data, ignore(c, 'url', 'data')),
);
const t =
(AxiosDomain.as.length +
AxiosDomain.asp.length +
AxiosDomain.asd.length) *
2 +
1;
expect(cb.mock.calls.length).toBe(t);
});
});

View File

@ -3,7 +3,7 @@ import { flattenHeaders } from 'src/core/flattenHeaders';
import Axios from 'src/core/Axios'; import Axios from 'src/core/Axios';
describe('src/core/flattenHeaders.ts', () => { describe('src/core/flattenHeaders.ts', () => {
const keys = [...Axios.as, ...Axios.pas, ...Axios.das]; const keys = [...Axios.as, ...Axios.asp, ...Axios.asd];
const baseHeaders = { const baseHeaders = {
options: { options: {
v1: 'options1', v1: 'options1',

View File

@ -4,7 +4,7 @@ import Axios from 'src/core/Axios';
describe('src/core/generateType.ts', () => { describe('src/core/generateType.ts', () => {
test('应该是一个 reuqest', () => { test('应该是一个 reuqest', () => {
for (const a of [...Axios.as, ...Axios.pas, ...Axios.das]) { for (const a of [...Axios.as, ...Axios.asp, ...Axios.asd]) {
expect(generateType({ method: a })).toBe('request'); expect(generateType({ method: a })).toBe('request');
} }
}); });