【问题标题】:Typescript ReturnType of generic function泛型函数的Typescript ReturnType
【发布时间】:2018-10-23 14:07:25
【问题描述】:

TypeScript 2.8 中的新 ReturnType 是一项非常有用的功能,可让您提取特定函数的返回类型。

function foo(e: number): number {
    return e;
}

type fooReturn = ReturnType<typeof foo>; // number

但是,我在泛型函数的上下文中使用它时遇到了问题。

function foo<T>(e: T): T {
    return e;
}

type fooReturn = ReturnType<typeof foo>; // type fooReturn = {}

type fooReturn = ReturnType<typeof foo<number>>; // syntax error

type fooReturn = ReturnType<(typeof foo)<number>>; // syntax error

有没有办法提取泛型函数会给出特定类型参数的返回类型?

【问题讨论】:

标签: typescript generics


【解决方案1】:

TypeScript 编译器不会将 typeof foo 视为泛型类型。我会说这是编译器中的一个错误。

然而,TypeScript 有callable interfaces,它可以是通用的,没有任何问题,所以如果你引入一个与你的函数签名兼容的可调用接口,你可以像这样实现你自己的ReturnType 等价物:

function foo<T>(x: T): T {
  return x;
}


interface Callable<R> {
  (...args: any[]): R;
}

type GenericReturnType<R, X> = X extends Callable<R> ? R : never;

type N = GenericReturnType<number, typeof foo>; // number

【讨论】:

  • 这个例子没什么用。您需要知道返回类型才能使用GenericReturnType 获取返回类型。
  • 这不是一个好的解决方案,因为它假定手头的函数只有一个泛型类型
  • 奇怪的是,一条称它无用的评论有 64 个赞成票,而答案几乎没有反对票。这是一个令人费解的混乱,只能返回一个传入的类型。type N = number; 将完全一样,没有所有的来回。
【解决方案2】:

如果你想得到一些特殊的泛型,你可以使用一个假函数来包装它。

const wrapperFoo = () => foo<number>()
type Return = ReturnType<typeof wrapperFoo>

更复杂的演示

function getList<T>(): {
  list: T[],
  add: (v: T) => void,
  remove: (v: T) => void,
  // ...blahblah
}
const wrapperGetList = () => getList<number>()
type List = ReturnType<typeof wrapperGetList>
// List = {list: number[], add: (v: number) => void, remove: (v: number) => void, ...blahblah}

【讨论】:

  • 您也可以在通用函数中使用此技术。 function foobar&lt;T&gt; { const wrapperGetList = () =&gt; getList&lt;T&gt;(); type List = ReturnType&lt;typeof wrapperGetList&gt;; ... }
  • 很好的解决方案!与来自formik 库的react hook useFormik 遇到了同样的问题,我需要将类型化的formik 实例作为道具传递给另一个组件。该解决方案有效:const useWrappedFormikForTypes = (...args: any) =&gt; useFormik&lt;MyFormValuesType&gt;(args); export type TypedFormikInstance = ReturnType&lt;typeof useWrappedFormikForTypes&gt;
【解决方案3】:

如果您可以更改函数定义,我发现了一种很好且简单的方法来实现这一点。 在我的情况下,我需要使用带有通用函数的打字稿类型Parameters,正是我正在尝试Parameters&lt;typeof foo&lt;T&gt;&gt;,但实际上它不起作用。因此,实现这一点的最佳方法是通过接口函数定义更改函数定义,这也可以使用打字稿类型ReturnType

以下是 OP 描述的案例:

function foo<T>(e: T): T {
   return e;
}

type fooReturn = ReturnType<typeof foo<number>>; // Damn! it throws error

// BUT if you try defining your function as an interface like this:

interface foo<T>{
   (e: T): T
}

type fooReturn = ReturnType<foo<number>> //it's number, It works!!!
type fooParams = Parameters<foo<string>> //it also works!! it is [string]

//and you can use the interface in this way
const myfoo: foo<number> = (asd: number) => {
    return asd;
};

myfoo(7);

【讨论】:

  • 我认为foo 也应该可以直接调用。 foo(e) 会出错
  • 这完全违背了目的。使用ReturnType 的全部原因是您可以从函数派生返回类型,而不必声明完全复制该类型的接口。当然,它适用于number,但是当foo 返回一个对象时,您不想为复杂类型创建一个接口。
【解决方案4】:

这是我目前提取未导出内部类型的导入库(如 knex)的有效解决方案:

// foo is an imported function that I have no control over
function foo<T>(e: T): InternalType<T> {
    return e;
}

class Wrapper<T> {
  // wrapped has no explicit return type so we can infer it
  wrapped(e: T) {
    return foo<T>(e)
  }
}

type FooInternalType<T> = ReturnType<Wrapper<T>['wrapped']>
type Y = FooInternalType<number>
// Y === InternalType<number>

【讨论】:

  • 顺便说一句,如果我们用class 替换interface,它就可以工作。感谢这个想法
  • 糟糕!感谢您指出错误。
  • 这很好地输入了 useFormik 的结果,通用参数完好无损。
【解决方案5】:
const wrapperFoo = (process.env.NODE_ENV === 'development' ? foo<number>() : undefined)!
type Return = typeof wrapperFoo

我什至会使用testtypescript_helper 之类的随机字符串而不是development 代替development(只要TypeScript 不抛出错误),这样它就永远不会执行,因为它不会在运行时使用。

【讨论】:

  • 这是使用硬编码类型执行函数,然后根据硬编码值推断该类型。它根本不派生任何东西。 type Return = number; 将产生相同类型的结果。但这需要为foo 的任何参数填充虚拟值,并且只能忍受它可能导致的任何副作用。如果foodb.query,而你的虚拟值是'DROP TABLE Students;'...
【解决方案6】:

我找到了解决方案。你决定它是否符合你的需要:)

使用接口声明你的函数参数和返回类型

interface Foo<T, V> {
  (t: T, v: V): [T, V]
}

使用ParametersReturnType 以这种方式实现您的功能

function foo<T, V>(...[t, v]: Parameters<Foo<T, V>>): ReturnType<Foo<T, V>> {
  return [t, v]; // [T, V]
}

正常调用你的函数,或者使用ReturnType获取返回类型

foo(1, 'a') // [number, string]
type Test = ReturnType<Foo<number, number>> // [number, number]

【讨论】:

    猜你喜欢
    • 2022-01-19
    • 2022-11-04
    • 2019-02-10
    • 2018-07-03
    • 2020-11-15
    • 2020-08-31
    • 2019-01-15
    • 2019-05-06
    相关资源
    最近更新 更多