泛型就像函数
函数
const f = (a, b) => a + b const result = f(1, 2) // 3
泛型
type F<A, B> = A | B type Result = F<string, number> // string | number

函数的本质是什么
函数的本质是推后执行的、部分待定的代码
// 立即执行 console.log(1) // 推后一步执行 const f1 = () => console.log(1) f1() // 推后两步执行 const f2 = () => console.log(1) console.log(2) f2() // 部分待定的 const f3 = (fn, n) => fn(n) // 参数待定 f3(console.log, 3)
泛型的本质是什么
泛型的本质是推后执行的、部分待定的类型
没有泛型的类型系统,就如同没有函数的编程语言
什么时候需要泛型
当我们需要准确的定义返回值的类型的时候,就需要泛型

这时候你可能会说,做类型收窄不就行了吗?就像下面这样
function echo(whatever: number | string | boolean) {
switch (typeof whatever) {
case 'number':
return whatever;
case 'string':
return whatever;
case 'boolean':
return whatever;
}
}
但是你会发现,这样写得到的返回值类型是不准确的
const a = echo(233); // 我们期望 a 的类型是 233 // 但 a 的类型却是 string | number | boolean
这时候,泛型就派上用场了
function echo<T>(whatever: T): T {
return whatever;
}
const a = echo(233);
// a 的类型为 233
简单泛型示例
type Union<A, B> = A | B;
type Union3<A, B, C> = A | B | C;
type Intersect<A, B> = A & B;
type Intersect3<A, B, C> = A & B & C;
interface List<A> {
[index: number]: A;
}
interface Hash<V> {
[key: string]: V;
}
条件类型(Conditional Types)

T extends string 可理解为 T <= string(T 包含于 string)
type R1 = LikeString<'hi'> // true
type R2 = LikeString<true> // false
type S1 = LikeNumber<2333> // 1
type S2 = LikeNumber<false> // 2
type T1 = LikePerson<{ name: 'ClariS', age: 18 }> // yes
type T2 = LikePerson<{ age: 1 }> // no
泛型中的特殊运算规则
现有如下泛型 ToArray
type ToArray<T> = T extends unknown ? T[] : never;
问:type Result = ToArray<string | number> 的类型是什么?
直接说答案:Result 为 string[] | number[]
你可能会疑惑,按照一般的理解,string | number 是包含于 unknown 的,那么 Result 就应该是 (string | number)[] 呀
按照通常的理解是没错,但因为这是在泛型中,因此规则会有些许不同
可以按照如下拆分过程来进行理解记忆:
type Result = ToArray<string | number>; // type Result = (string | number) extends unknown ? ... // type Result = (string extends unknown ? ...) | (number extends unknown ? ...) // type Result = string[] | number[]
即泛型中的联合类型会分开进行运算(extends 运算),这就好像是乘法中的分配率 (A + B) X C = A X C + B X C
再问:type Result2 = ToArray<never> 的类型是什么?
答案是 never
同样按照一般的理解,通常情况下(非泛型中),never 是包含于 unknown 的,那么 Result 应该是 never[] 才对
但 Result 在此处却为 never
type Result = ToArray<never>; // never
即泛型中的 never 进行任何运算(extends 运算或者 & 运算)都只会得到 never,这就好像是乘法中的零 0 X C = 0
注意:以上讨论的规则只对泛型有效
在非泛型中,使用 extends 运算会得到不一样的结果
// 非泛型 type T0 = never extends never ? true : false // ^? type T0 = true // 泛型 type IsNever<T> = T extends never ? true : false type T1 = IsNever<never> // ^? type T1 = never
那么,如何才能禁用泛型中的自动拆分规则呢?
我们可以构造一个类型,在外面包裹上一个新的类型来防止 TypeScript 遍历 extends 左侧类型的行为,比如下面的 []
type IsNever<T> = [T] extends [never] ? true : false type T0 = IsNever<never> // ^? type T0 = true type ToArray<T> = [T] extends unknown ? T[] : never; type Result1 = ToArray<string | number>; // ^? type Result1 = (string | number)[] type Result2 = ToArray<never>; // ^? type Result2 = never[]
在泛型中使用 keyof
keyof 关键字用于获取对象类型的所有键的联合类型
type Person = { name: string; age: number };
type GetKeys<T> = keyof T;
type Result = GetKeys<Person>;
// ^-- 'name' | 'age'
常和映射类型一起使用
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
keyof 也可用于数组 / 元组
type A = keyof [1, 2, 3]; // A 的类型为 "0" | "1" | "2" | "length" | "toString" | "pop" | ...
在泛型中使用 extends keyof
type Person = { name: string; age: number };
type GetKeyType<T, K extends keyof T> = T[K];
type Result1 = GetKeyType<Person, 'name'>; // string
type Result2 = GetKeyType<Person, 'age'>; // number
type Result3 = GetKeyType<Person, 'name' | 'age'>; // string | number
K extends keyof T 的写法称为泛型约束,GetKeyType 第二个参数 k 的类型必须包含于 string | number
其他
PropertyKey
PropertyKey 是 TS 中的一个内置类型,它表示可以用作对象属性键的类型。它是 string | number | symbol 类型的别名,因为这些类型都可以用作对象属性的键。
PropertyKey 类型常用于泛型约束,以确保泛型类型参数只能是可以用作对象属性键的类型。例如,定义一个泛型类型 Foo<K extends PropertyKey>,其中 K 只能是 string、number 或 symbol 类型。
T[number]
type A = [1, 2, 3] type B = A[number] // 3 | 1 | 2,无序
A[number] 表示获取类型 A 的数字索引签名的类型。由于元组类型具有隐式的数字索引签名,因此 A[number] 的类型为元组中所有元素类型的联合类型 1 | 2 | 3
评论区
评论加载中...