【问题标题】:TypeScript - generic being incorrectly inferred as unknownTypeScript - 泛型被错误地推断为未知
【发布时间】:2020-05-05 15:27:38
【问题描述】:

我有以下通用函数:

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

我知道我可以指定类型参数。我想知道为什么它们被错误地推断出来,以及我是否可以以某种方式更改实现以帮助推断机制。

【问题讨论】:

    标签: typescript


    【解决方案1】:

    来自TypeScript specifications

    类型参数可以在参数类型和返回类型注释中被引用,但不能在类型参数约束中被引用。

    鉴于您的函数签名,

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

    ,我对上述语句的解释是,T 出现在参数R 的类型参数约束签名extends (...args: any) =&gt; AxiosPromise&lt;T&gt; 中,因此无法正确解析。 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
    

    在接下来的两个示例中,编译器将T 推断为unknown,因为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

    【讨论】:

    • 你说的“这些不是”是什么意思? (回复editing your question,不在 cmets 中(视情况而定)。)
    • 谢谢,这是打字稿中一个非常令人困惑的部分,这解释了绑定匹配与使用泛型之间的区别。
    【解决方案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
      }
    

    【讨论】:

      【解决方案3】:

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

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

      如果你这样做,它就会知道数据可以是Foo | undefined

      【讨论】:

      • 我知道我可以只传递类型参数,我试图弄清楚为什么它们被错误地推断出来
      猜你喜欢
      • 2020-07-23
      • 1970-01-01
      • 2017-10-04
      • 2019-09-17
      • 2020-09-22
      • 2019-08-21
      • 1970-01-01
      • 2016-12-05
      • 2014-06-21
      相关资源
      最近更新 更多