TypeScript-泛型被错误地推断为未知

时间:2019-11-09 12:37:52

标签: typescript

我具有以下通用功能:

export function useClientRequest<T, R extends (...args: any) => AxiosPromise<T>>(
  func: R,
  ...args: Parameters<R>
): [T | undefined, boolean, AxiosError | undefined] {
  // Irrelevant
}

概括地说,该函数的返回包含一个T类型的值,该值应如上所述进行推断。

然后我尝试如下使用它:

interface Foo {
  // ...
}

function fooGetter(url: string): AxiosPromise<Foo> {
  return Axios.get<Foo>(url);
}

const [data] = useClientRequest(fooGetter, 'url.com');

但是我的IDE报告data类型为unknown,因为T被推断为unknown

我做错什么了吗?这是TypeScript的限制吗?

Typescript v3.7.2

我知道我可以指定类型参数。我想知道为什么不正确地推断它们,以及是否可以通过某种方式更改实现以帮助推断机制。

3 个答案:

答案 0 :(得分:3)

来自TypeScript specifications

  

类型参数可以在引入它们的调用签名的参数类型和返回类型注释中引用,但不能在类型参数约束中引用。

给出您的功能签名,

<T, R extends (...args: any) => AxiosPromise<T>>(
  func: R, ...args: Parameters<R>
): [T | undefined, boolean, AxiosError | undefined]

,我对以上陈述的解释是,T出现在参数extends (...args: any) => AxiosPromise<T>的类型参数约束签名R中,因此无法正确解析。 unknown只是通用类型参数的implicit default constraint type

因此这些人为的示例将起作用:

declare function fn0<T, U extends T>(fn: (t: T) => U): U
const fn0Res = fn0((arg: { a: string }) => ({ a: "foo", b: 42 }))  // {a: string; b: number;}

declare function fn1<T, F extends (args: string) => number>(fn: F, t: T): T
const fn1Res = fn1((a: string) => 33, 42) // 42

在接下来的两个示例中,编译器推断Tunknown,因为T仅在U的调用签名约束中引用,而未在函数参数中使用进一步的编译器提示的代码位置:

declare function fn2<T, U extends (args: T) => number>(fn: U): T
const fn2Res = fn2((arg: number) => 32) // T defaults to unknown

declare function fn3<T, U extends (...args: any) => T>(fn: U): T
const fn3Res = fn3((arg: number) => 42) // T defaults to unknown

可能的解决方案(选择最合适的解决方案)

1。)您可以仅为函数参数和返回类型引入类型参数TR

declare function useClientRequest2<T, R extends any[]>(
    func: (...args: R) => Promise<T>,
    ...args: R
): [T | undefined, boolean, AxiosError | undefined]

const [data] = useClientRequest2(fooGetter, 'url.com'); // data: Foo | undefined

2。)这是条件类型的替代方法(更详细):

declare function useClientRequestAlt<R extends (...args: any) => Promise<any>>(
    func: R,
    ...args: Parameters<R>
): [ResolvedPromise<ReturnType<R>> | undefined, boolean, AxiosError | undefined]

type ResolvedPromise<T extends Promise<any>> = T extends Promise<infer R> ? R : never
const [data2] = useClientRequestAlt(fooGetter, 'url.com'); // const data2: Foo | undefined

Playground

答案 1 :(得分:2)

我无法回答为什么它不起作用。但是,我会做这样的事情:

type UnpackedAxiosPromise<T> = T extends AxiosPromise<infer U> ? U : T; 

function useClientRequest<R extends (...args: any) => any>(
    func: R,
    ...args: Parameters<R>
  ): [UnpackedAxiosPromise<ReturnType<R>> | undefined, boolean, AxiosError | undefined] {
    //irrelevant
  }

答案 2 :(得分:0)

您没有将T传递给该函数,因此它不知道它是什么。检查以下代码:

const [data] = useClientRequest<Foo, typeof fooGetter>(fooGetter, 'url.com');

如果您这样操作,它将知道数据可以为Foo | undefined