带有重载的实现签名的Typescript函数重载在外部不可见



问题

如何正确地公开重载实现签名?

示例

这个问题的基础:

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也适用于第二种解决方案。

最新更新