TypeScript:是否可以获取泛型函数的返回类型?

时间:2018-10-24 07:55:05

标签: typescript generics typeof

我已经从某个模块中导出了一个函数,如下所示:

export function MyFunc<A>() {
    return {
        foo: (in: A) => void
    }
}

现在,在其他模块中,我希望能够讨论MyFunc的不同返回类型。由于没有导出类型,因此我将使用typeof来获取要赋予值MyFunc的类型。 理想情况下,我会执行以下操作

import { MyFunc } from "mymodule";
type MyFuncReturned<A> = ReturnType<typeof MyFunc<A>>;

function foo(): MyFuncReturned<string> {
   // ...
}

麻痹,这行不通; typeof只能传递一个值,不喜欢我尝试指定该值的通用类型。

我能做的最好的就是说服TypeScript从我创建的值中推断出MyFunc的特定类型,然后为它们提供单独的类型别名,例如:

const myFuncStringReturn = MyFunc<string>();
type MyFuncStringReturn = typeof myFuncStringReturn;

为了避免仅仅为了获取类型信息而实际运行MyFunc,我可以将其隐藏在函数后面并在其上使用ReturnType

const myFuncStringReturn = () => MyFunc<string>();
type MyFuncStringReturn = ReturnType<typeof myFuncStringReturn>;

const myFuncBoolReturn = () => MyFunc<bool>();
type MyFuncBoolReturn = ReturnType<typeof myFuncBoolReturn>;

这给了我一种方式,一次谈论MyFunc的不同返回类型,但是

  • 涉及TS可以从中推断出的实际运行时代码。
  • 不要让我更笼统地谈论MyFunc

我能想到的唯一“适当”的解决方案是在声明MyFunc时复制一堆类型信息:

export function MyFunc<A>(): MyFuncReturns<A> {
    return {
        foo: (in: A) => void
    }
}

export type MyFuncReturns<A> = {
    foo: (in: A) => void
}

但是现在更改MyFunc时,我必须确保与MyFuncReturns保持同步。

仅通过导出值MyFuncReturns<A>,是否可以通过某种方式获得MyFunc这样的类型,而无需添加运行时代码或在上面添加样板?

3 个答案:

答案 0 :(得分:4)

Titian's solution above如果仅将泛型仅应用在函数体内,则效果很好。

但是,在某些情况下,通用类型是参数和/或返回类型的一部分。

max_length=142

因此,为了推广Titian的解决方案并支持固定任何泛型函数的参数和返回类型,我编写了以下内容:

所需的实用程序类型

function MyFunc3<T extends object | number | string>(r: number, p: T, x: boolean): T {
    return p;
}

示例函数

// From https://stackoverflow.com/a/53808212 by jcalz (https://stackoverflow.com/users/2887218)
export type IfEquals<T, U, Y=unknown, N=never> =
    (<G>() => G extends T ? 1 : 2) extends
    (<G>() => G extends U ? 1 : 2) ? Y : N;

// Aidin: Please comment if you could make the following shorter!
type ReplaceType<T, FROM_TYPE, TO_TYPE> = IfEquals<T, FROM_TYPE, TO_TYPE, T>;
type ReplaceTypeInArray<ARR, F, T> =
    ARR extends [] ? []
    : ARR extends [infer P0] ? [P0 extends F ? T : P0]
    : ARR extends [infer P0, infer P1] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>]
    : ARR extends [infer P0, infer P1, infer P2] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>]
    : ARR extends [infer P0, infer P1, infer P2, infer P3] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>]
    : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>]
    : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>]
    : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>]
    : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>]
    : ARR extends [infer P0, infer P1, infer P2, infer P3, infer P4, infer P5, infer P6, infer P7, infer P8] ? [ReplaceType<P0, F, T>, ReplaceType<P1, F, T>, ReplaceType<P2, F, T>, ReplaceType<P3, F, T>, ReplaceType<P4, F, T>, ReplaceType<P5, F, T>, ReplaceType<P6, F, T>, ReplaceType<P7, F, T>, ReplaceType<P8, F, T>]
    : never;

修复示例

type ALL = string | number | object | boolean;

export function MyFunc1<T extends ALL>() {
  return {
    foo: (os : T) => {}
  }
}

function MyFunc2<T extends ALL>(r: 55, p: T, x: boolean): T {
    return p;
}

Playground Link (Contains all 3 parts)

现在,您可以在任何这些固定功能类型上简单地使用// Inspired by https://stackoverflow.com/a/52964723 by Titian (https://stackoverflow.com/users/125734) class Helper1 <T extends ALL> { Fixate = (...args: ReplaceTypeInArray<Parameters<typeof MyFunc1>, ALL, T>) => MyFunc1<T>(...args); } type FixatedFunc1<T extends ALL> = Helper1<T>['Fixate']; // -- Usage type ForNumber1 = FixatedFunc1<number> // {foo: (os: number) => void;} type ForString1 = FixatedFunc1<string> // {foo: (os: string) => void;} // ~~~~~~~~~~~~~~~~~~~ class Helper2 <T extends ALL> { Fixate = (...args: ReplaceTypeInArray<Parameters<typeof MyFunc2>, ALL, T>) => MyFunc2<T>(...args); } type FixatedFunc2<T extends ALL> = Helper2<T>['Fixate']; // -- Usage type ForNumber2 = FixatedFunc2<number> // (args_0: 55, args_1: number, args_2: boolean) => number type ForString2 = FixatedFunc2<string> // (args_0: 55, args_1: string, args_2: boolean) => string ReturnType<T>

答案 1 :(得分:1)

有人建议允许将typeof与任意表达式一起使用,以允许诸如获取特定类型参数的泛型函数的返回类型之类的事情(请参见herehere)< / p>

今天可以使用的更通用的变通方法是使用具有与该函数的返回类型绑定的字段的通用类。然后,我们可以提取类的字段。因为对于类,我们可以在类型表达式中指定泛型类型参数,所以我们可以提取返回类型的泛型形式:

export function MyFunc<A>() {
  return {
    foo: (os : A) => {}
  }
}

class Helper <T> {
  Return = MyFunc<T>()
}
type FuncReturnType<T> = Helper<T>['Return']
type ForBool = FuncReturnType<boolean> //  {foo: (os: boolean) => void;}
type ForString = FuncReturnType<string> //  {foo: (os: string) => void;}

注意,如果您有A的约束,则需要在THelper中复制FuncReturnType上的约束,这是不可避免的

答案 2 :(得分:1)

通过扩展Aidin的答案,我设法制作了ReplaceTypeIn,它适用于任意数量的元素,对象以及数组,并且仅使用几行:

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Derived from https://stackoverflow.com/a/53808212 by jcalz (https://stackoverflow.com/users/2887218)
export type IfEquals<T, U, Y=unknown, N=never> =
    (<G>() => G extends T ? 1 : 2) extends
    (<G>() => G extends U ? 1 : 2) ? Y : N;

type ReplaceType<T, FROM_TYPE, TO_TYPE> = IfEquals<T, FROM_TYPE, TO_TYPE, T>;
type ReplaceTypeIn<T, FROM_TYPE, TO_TYPE> = {
  [K in keyof T]: ReplaceType<T[K], FROM_TYPE, TO_TYPE>;
};

// ~~~~~~~~~~~~~~~~~~~~~~ SAMPLE FUNCTIONS ~~~~~~~~~~~~~~~~~~~

export function MyFunc1<T extends unknown>() {
  return {
    foo: (os: T) => {}
  }
}

function MyFunc2<T extends unknown>(r: 55, p: T, x: boolean): T {
    return p;
}

// ~~~~~~~~~~~~~~~~~~~~~ FIXATIONS ~~~~~~~~~~~~~~~~~~~~

// Derived from https://stackoverflow.com/a/52964723 by Titian (https://stackoverflow.com/users/125734)
class Helper1 <T extends unknown> {
    Fixate = (...args: ReplaceTypeIn<Parameters<typeof MyFunc1>, unknown, T>) => MyFunc1<T>(...args);
}
type FixatedFunc1<T extends unknown> = Helper1<T>['Fixate'];

// -- Usage

type ForNumber1 = FixatedFunc1<number> //  {foo: (os: number) => void;}
type ForString1 = FixatedFunc1<string> //  {foo: (os: string) => void;}

// ~~~~~~~~~~~~~~~~~~~

class Helper2 <T extends unknown> {
    Fixate = (...args: ReplaceTypeIn<Parameters<typeof MyFunc2>, unknown, T>) => MyFunc2<T>(...args);
}
type FixatedFunc2<T extends unknown> = Helper2<T>['Fixate'];

// -- Usage

type ForNumber2 = FixatedFunc2<number> //  (r: 55, p: number, x: boolean) => number
type ForString2 = FixatedFunc2<string> //  (r: 55, p: string, x: boolean) => string

在上面的示例中,这里是playground link