使用对象重载 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 可能相关。

相关链接

Typescript Playground link

解决方案

为了解决这个问题,我使用了条件类型。例如:

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