使用对象重载 Typescript 函数 - 重载的实现签名在外部不可见
Typescript function overloading with object - Implementation signatures of overloads are not externally visible
问题
如何正确公开重载实现签名?
例子
以此为基础 :
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 [];
}
如何封装此函数,同时根据选项参数保留可能的 return 类型?
例如:
function wrapFunction(arg1 arg2, options) {
// do something with arg1 and arg2
return get(options)
}
根据 wrapFunction 方法中为选项设置的值,return 类型将基于具有相同值的 return 类型的 get。
即:
const g = wrapFunction(1,2, {asObject: true})
// Should return the same thing as get({asObject:true})
尝试的解决方案
我可以简单地为 wrapFunction 重写一个新签名,但这会非常冗长,尤其是当我有许多类型的 wrapFunctions 遵循相同的嵌套 get
调用模式时。
一个 是将 wrapFunction 类型转换为 typeof get
但这删除了修改 wrapFunction 的参数列表的能力。
This 可能相关。
相关链接
解决方案
为了解决这个问题,我使用了条件类型。例如:
// 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 })
完整对比可见here
想法
这些是 issue 的解决方法,但足以解决问题。对于复杂的场景,或者可能是隐藏的 API,使用 currying 方法看起来更干净,避免了对许多重载方法的需要。同样,更基本的场景或 public API 适用于第二种解决方案。
问题
如何正确公开重载实现签名?
例子
以此为基础
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 [];
}
如何封装此函数,同时根据选项参数保留可能的 return 类型?
例如:
function wrapFunction(arg1 arg2, options) {
// do something with arg1 and arg2
return get(options)
}
根据 wrapFunction 方法中为选项设置的值,return 类型将基于具有相同值的 return 类型的 get。
即:
const g = wrapFunction(1,2, {asObject: true})
// Should return the same thing as get({asObject:true})
尝试的解决方案
我可以简单地为 wrapFunction 重写一个新签名,但这会非常冗长,尤其是当我有许多类型的 wrapFunctions 遵循相同的嵌套 get
调用模式时。
一个 typeof get
但这删除了修改 wrapFunction 的参数列表的能力。
This 可能相关。
相关链接
解决方案
为了解决这个问题,我使用了条件类型。例如:
// 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 })
完整对比可见here
想法
这些是 issue 的解决方法,但足以解决问题。对于复杂的场景,或者可能是隐藏的 API,使用 currying 方法看起来更干净,避免了对许多重载方法的需要。同样,更基本的场景或 public API 适用于第二种解决方案。