网站搜索

typescript - 即使未提供类型,泛型函数也能获取类型


我不明白为什么一个泛型函数会自动获得泛型类型,而不需要向它提供任何东西。

type T<G = Record<string, any>> = {
     id: keyof G
     label: string
     somethingElse?: string
}

该函数返回一个对象,该对象具有一个特殊属性 id,如果未传递 source 属性,则该属性应具有类型 "random"。如果传递了 source,返回的 id 应继承 source 的类型

const element = <ID extends string = 'random'>({ source }: { source?: ID } = {}) =>
    ({
        id: (source || 'random') as ID,
        label: 'Element1',
    }) as const satisfies T  
    // I don't want the return type to be "T", I want what it returns to satisfy "T".
    // as you see I dont provide "somethingElse" key here, so I don't want it in the return type

type Data = { a: string; b: number }

这给了我我想要的确切错误,但只有在我使用变量时才会出现:

const el1 = element()                   // el1 gets type { readonly id: "random";  readonly label: "Element1"; }
const el2 = element({source: 'b'})     // el2 gets type { readonly id: "b";  readonly label: "Element1"; }
const el3 = element({source: 'else'})   // el3 gets type { readonly id: "else";  readonly label: "Element1"; }

const b: T<Data>[] = [el, el2, el3]   // el1 and el3 invalid, el2 valid. It is all correct

但是为什么当我直接使用它而不使用变量时这是有效的?它应该抛出一个错误

const a: T<Data>[] = [element()]   
// No generic or property is provided to it "element()", 
// why the return type of "element()" is { readonly id: keyof Data; readonly label: "Element1"; } ?? 

TypeScript 游乐场

答案#1

TypeScript 可以将函数返回类型用作推断目标,这意味着泛型函数可以通过其返回的上下文类型以及已知的参数类型来推断其类型参数。

const a: T<Data>[] = [element()]   

编译器期望 [element()] 的类型是 T<Data>[],因此 element() 的类型是 T<Data>,因此 ID 是 Data 的 keyof,并且没有错误。


如果你不喜欢那样,并且你希望返回类型 不 被用作推断的使用位置,你可以使用 NoInfer 工具类型来阻止从该位置的推断:

const element = <ID extends string = 'random'>({ source }: { source?: ID } = {}) =>
   ({
       id: (source || 'random') as NoInfer<ID>, // <-- here
       label: 'Element1',
   }) as const satisfies T  

在 ID 未从返回类型推断的情况下,这应该没有影响,而且你所有的其他例子都是一样的:

const el1 = element()             
// el1 gets type { readonly id: "random";  readonly label: "Element1"; }. 
const el2 = element({source: 'b'})   
// el2 gets type { readonly id: "b";  readonly label: "Element1"; }.
const el3 = element({source: 'else'})   
// el3 gets type { readonly id: "else";  readonly label: "Element1"; }.
const b: T<Data>[] = [el1, el2, el3]   // el1 and el3 invalid, el2 valid. 

但是现在在你的最后一个示例中会得到期望的错误,因为 T<Data> 上下文不会影响 ID。并且由于 ID 现在根本没有推断位置,它会回退到默认值 “random”:

const a: T<Data>[] = [element()]  // error!
// Type '{ readonly id: "random"; readonly label: "Element1"; }' 
// is not assignable to type 'T<Data>'.

代码的 Playground 链接