我正在研究一些库(某种API代理),该库将在其他内部项目中使用。我想定义一些方法以便于使用,但是我很难确定类型断言:
首先,我为所有终结点方法提供了一个工厂:
export type Params<TQueryParams> = {
customConfig?: object;
fullResponse?: boolean;
} & (TQueryParams extends undefined ? { queryParams?: undefined } : { queryParams: TQueryParams })
export const getFactory = <
TQueryParams extends object | undefined = undefined
>({ path }: any) => async (
{ queryParams, customConfig, fullResponse = false }: Params<TQueryParams>
): Promise<any> => {
// some irrelevant factory code
return {};
};
我已经使用了一些魔术,所以取决于TQueryParams
,是否需要它,但这还不够。
现在,当我想定义所有端点并使用工厂时:
// Case 1 - no interface
export const getFile = getFactory({
path: '/api/file'
});
// I'd like for this one to not throw error
getFile();
// These will not throw error - as intended
getFile({});
getFile({
fullResponse: true
});
// This will throw error - as intended
getFile({
queryParams: {}
});
// Case 2 - interface with all optional props
export interface ImageQueryParams {
width?: number;
height?: number;
}
export const getImage = getFactory<ImageQueryParams>({
path: '/api/image'
});
// All of them should be ok, but only last will not throw error
getImage();
getImage({});
getImage({
fullResponse: true
});
getImage({
queryParams: {}
});
// Case 3 - interface with mandatory props
export interface DataQueryParams {
id: number;
sort?: string;
}
export const getData = getFactory<DataQueryParams>({
path: '/api/data'
});
// All will throw error - as intended
getData();
getData({});
getData({
fullResponse: true
});
getData({
queryParams: {}
});
// This one will not throw error - as intended
getData({
queryParams: {
id: 2131241
}
});
只有第3种情况可以正常工作。
如果没有接口或它包含所有可选道具,我将特别想实现一个解决方案,那么我将什么也不能传递(getImage();
)并且没问题。
答案 0 :(得分:2)
我对这些类型定义并不感到高兴(我希望有一些简化和通用的东西),但它们似乎适用于您的用例:
type CanBeOmitted<T, Y = T, N = never> =
{} extends T ? Y : // T is weak (all props are optional), or
undefined extends T ? Y : // T can be undefined
N;
类型别名CanBeOmitted<T, Y, N>
检查T
是全选对象类型(也称为"weak type")还是兼容undefined
的类型。如果是这样,则返回Y
;如果不是,则返回N
(默认为never
)。我们可以多次使用它来建立您的Params
类型。
这里是:
export type Params<TQueryParams> =
CanBeOmitted<TQueryParams, [undefined?]> |
[
{
customConfig?: object,
fullResponse?: boolean
} & (
{ queryParams: TQueryParams } |
CanBeOmitted<TQueryParams, { queryParams?: never }>
)
];
我做过的另一件事是使它成为包含参数的元组类型。如果可以完全省略该参数,则返回optional tuple。
这里是getFactory
:
export const getFactory = <
TQueryParams extends object | undefined = undefined
>({ path }: any) => async (
...args: Params<TQueryParams>
): Promise<any> => {
// get rid of conditional types
const arg = args[0] as {
customConfig?: object,
fullResponse?: boolean,
queryParams?: TQueryParams
} | undefined;
const queryParams = arg ? arg.queryParams : undefined;
const customConfig = arg ? arg.customConfig : undefined;
const fullResponse = (arg ? arg.fullResponse : undefined) || false;
// some irrelevant factory code
return {};
};
请注意,Params<TQueryParams>
被用作rest tuple而不是单个参数。当该元组是可选的时,它允许使用零参数调用getFactory()
的返回值。还要注意,我们不能再假设该参数存在,因此我们必须更改如何将queryParams
,customConfig
和fullResponse
作为实现内的变量。
好的,让我们看看它是如何工作的:
// Case 1 - no interface
export const getFile = getFactory({
path: '/api/file'
});
getFile(); // okay
getFile({}); // okay
getFile({
fullResponse: true
}); // okay
getFile({
queryParams: {}
}); // error, {} is not undefined
// Case 2 - interface with all optional props
export interface ImageQueryParams {
width?: number;
height?: number;
}
export const getImage = getFactory<ImageQueryParams>({
path: '/api/image'
});
getImage(); // okay
getImage({}); // okay
getImage({
fullResponse: true
}); // okay
getImage({
queryParams: {}
}); // okay
export interface DataQueryParams {
id: number;
sort?: string;
}
// Case 3 - interface with mandatory props
export const getData = getFactory<DataQueryParams>({
path: '/api/data'
});
getData(); // error, expected 1 arg
getData({}); // error, queryParams missing
getData({
fullResponse: true
}); // error, queryParams missing
getData({
queryParams: {}
}); // error, id missing
getData({
queryParams: {
id: 2131241
}
}); // okay
我认为就是您要的东西。好的,希望对您有所帮助。祝你好运!