问题
如何正确地公开重载实现签名?
示例
这个问题的基础:
interface MyMap<T> {
[id: string]: T;
}
type Options = {
asObject?: boolean,
other?: Function
testing?: number
};
function get(options: { asObject: true, other?: Function, testing?:number }): MyMap<any>;
function get(options: { asObject?: false, other?: Function, testing?:number }): number[];
function get(): any[];
function get(options: Options = { asObject: false }): any[] | MyMap<any> {
if (options?.asObject) return {} as MyMap<any>;
return [];
}
如何包装此函数,但根据options参数保留可能的返回类型?
例如:
function wrapFunction(arg1 arg2, options) {
// do something with arg1 and arg2
return get(options)
}
根据wrapFunction方法中为选项设置的值,返回类型将基于具有相同值的get的返回类型。
即:
const g = wrapFunction(1,2, {asObject: true})
// Should return the same thing as get({asObject:true})
尝试的解决方案
我可以简单地为wrapFunction重写一个新的签名,但这将非常冗长,尤其是如果我有许多类型的wrapFunction,它们遵循嵌套get
调用的相同模式。
一个建议是将wrapFunction类型转换为typeof get
,但这将删除修改wrapFunction参数列表的功能。
这可能是相关的。
相关链接
打字游戏场链接
解决方案
为了解决这个问题,我使用了条件类型。例如:
// This is the switch interface
interface Switch<T> {
on: T
}
// This is the conditional type.
type ExtendReturn<E extends boolean, R extends string | number > = E extends true
? Array<R>
: number;
/*
First solution, using the conditional type and type inference. This requires a
nested structure and returns a curry-like function. The inner function separates out all the generics that should be inferred, and the outer function contains the generic that should be set explicitly
*/
function test3<R extends string | number = string>() {
return function inner<E extends boolean>(options: Switch<E>): ExtendReturn<E, R> {
const r = options.on ? 's' : 4
return r as ExtendReturn<E, R>
}
}
// Notice the extra (), but each of the types are inferred correctly
const a = test3<number>()({ on: true })
/*
Second Solution, using a combination of overload methods, and the conditional
type. This is a bit more verbose, but removes the requirement of the currying
structure, keeping a cleaner API.
*/
function test4<R extends string | number = string>(options?: Switch<false>): ExtendReturn<false, R>;
function test4<R extends string | number = string>(options: Switch<true>): ExtendReturn<true, R>;
function test4<E extends boolean, R extends string | number = string>(options?: Switch<E>): ExtendReturn<E, R> {
const r = options?.on ? 's' : 4
return r as ExtendReturn<E, R>
}
// Notice the simpler API
const m = test4<string>({ on: true })
完整的比较可以在这里看到
思想
这些是解决此问题的变通方法,但足以解决问题。对于复杂的场景,或者可能是隐藏的API,使用currying方法看起来更干净,避免了对许多重载方法的需要。类似地,更基本的场景或公共API也适用于第二种解决方案。