泛型参数放置之间的打字稿差异

typescript difference between placement of generics arguments

泛型函数语法有什么区别:

type Identity<T> = (t: T) => T

type Identity = <T>(t: T) => T

?

TypeScript 中有两种不同风格的 generics:泛型 functions 和泛型 types.


泛型函数在函数的调用签名上声明其泛型类型参数(或多个参数):

type IdentityGenFunc = <T>(t: T) => T

泛型函数(上面的 T)的类型参数直到实际调用函数时才会指定,此时调用者指定它(或编译器代表调用者推断它) .这意味着通用函数实现必须能够处理函数调用者想要的任何可能的 T 规范:

const idGenFunc: IdentityGenFunc = x => x;

const resultABC = idGenFunc("ABC"); // T inferred here as ABC
// const resultABC: "ABC"
const result123 = idGenFunc(123); // T inferred here as 123
// const result123: 123

泛型 type 在类型声明中声明其泛型类型参数(或多个参数):

type IdentityGenType<T> = (t: T) => T

泛型类型(T 以上)的类型参数必须先指定,然后您才能拥有该类型的值。如果泛型类型具有引用类型参数的调用签名或方法,则在调用该方法之前,只要指定了周围的泛型类型,该类型参数就会固定。一旦您谈论 IdentityGenType<string>,那么它的调用签名只需要能够处理 string。因此,要实现该功能,您不需要处理 T:

的任何可能值
const idGenTypeString: IdentityGenType<string> = x => x + "!";

const resultABCagain = idGenTypeString("ABC"); // okay, "ABC" is assignable to string
// const resultABCagain: string
const result123Again = idGenTypeString(123); // error! 123 is not assignable to string

这两种类型的泛型是相互关联的。类型 IdentityGenFunc 本质上是 IdentityGenType<T> 对每个可能的 T 的“无限交集”。 TypeScript 类型系统的表现力不足以说明这一点:

// the following is not valid TS, but this is what you want to say
type IdentityGenFunc = forall T, IdentityGenType<T>; // not valid TS, error

但你可以通过作业见证它:

// every IdGenFunc is also an IdGenType<number>
const idGenTypeNumber: IdentityGenType<number> = idGenFunc; // okay

另一方面,您可以认为 IdentityGenType<T>IdentityGenFunc 实例化 具有某种类型 T 相同。同样,类型系统不允许您这样表达:

// the following is not valid TS, but this is what you want to say
type GenType<T> = instantiate GenFunc with T; // not TS, error

尽管 TypeScript 4.7 将引入 instantiation expressions,这将使您可以在类型为通用函数的 value 上实例化类型参数:

// if you instantiate a value of type IdGenFunc with Date, then you get an IdGenType<Date> 
const idGenTypeDate: IdentityGenType<Date> = idGenFunc<Date>; // okay for TS4.7+

Playground link to code