泛型
泛型(Generics)在编程语言中是一个较为普遍的概念,在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。这给软件工程带来了极高的灵活性,进一步提高了组件或函数的可重用性。那么泛型具体的定义是什么呢?
泛型是指不预先确定的数据类型,具体的类型在使用的时候才能确定,它允许同一个函数可以接受不同类型参数的一个模板。设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
一、泛型函数
1 | function log<T>(value: T): T { |
如上所示,当我们调用 log<string>('hello')
时,string 类型就像参数一样,它将在出现 T
的任何位置填充该类型。图中 <T>
内部的 T
被称为类型变量,它是我们希望传递给 log 函数的类型占位符,同时它被分配给 value 参数和函数返回值用来代替它的类型:此时 T
充当的是类型,而不是特定的 string 类型。
我们除了可以这样显式定义泛型函数,还可以先通过类型别名指定泛型函数类型,然后指定函数实现,举个🌰:
1 | type Log = <T, U>(value: T, comment: U) => T; |
二、泛型接口
上面函数我们也可以通过泛型接口实现定义:
1 | // 这儿接口与类型别名完全一致 |
在上述示例的接口中泛型仅仅约束了一个函数,我们也可以用泛型来约束接口的其他成员,举个🌰:
1 | interface Obj<T> { |
除此之外我们还可以为泛型接口指定一个默认类型,举个🌰:
1 | interface Obj<T = number> { |
三、泛型类
与接口类似,泛型还可以约束类的成员,举个🌰:
1 | // 我们将泛型放在类的后面这样就可以约束类的所有成员了 |
此处需要注意的是泛型约束不能作用于静态属性和方法,举个🌰:
1 | class Greeter<T> { |
四、泛型约束
在部分情况下我们需要对泛型做一些约束,这个时候我们就需要用到泛型约束,举个🌰:
1 | function log<T> (value: T): T { |
五、泛型工具
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等等,不过在具体介绍之前,我们得先介绍一些相关的基础知识:
1、基础知识
(1) typeof
在 TypeScript 中,typeof 操作符可以用来获取一个变量声明或对象的类型,举个🌰:
1 | interface Person { |
(2) keyof
keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
1 | interface Person { |
在 TypeScript 中支持两种索引签名,数字索引和字符串索引:
1 | interface StringArray { |
为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript在执行索引操作时,会先把数值索引先转换为字符串索引。所以 keyof { [x: string]: Person }
的结果会返回 string | number
。
(3) in
in 用来遍历枚举类型
1 | type Keys = "a" | "b" | "c" |
(4) infer
infer 表示在 extends 条件类型语句中待推断的类型变量,简单举个🌰:
1 | type ParamType<T> = T extends (param: infer P) => any ? P : T; |
在这个条件语句 T extends (param: infer P) => any ? P : T
中,infer P
表示待推断的函数参数。
整句表示为:如果 T 能赋值给 (param: infer P) => any
,则结果是 (param: infer P) => any
类型中的参数 P
,否则返回为 T
。
1 | interface User { |
2、Partial
Partial<T>
的作用就是将某个类型里的属性全部变为可选项
1 | type Partial<T> = { |
在以上代码中,首先通过 keyof T
拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P]
取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。举个🌰:
1 | interface Todo { |
3、Required
Required<T>
的作用是将传入的属性变为必选项
1 | type Required<T> = { [P in keyof T]-?: T[P] }; |
这里的 -?
就是将可选项代表的 ?
去掉, 从而让这个类型变成必选项. 与之对应的还有个 +?
, 这个含义自然与 -?
之前相反, 它是用来把属性变成可选项的。举个🌰:
1 | interface Todo { |
4、Readonly
Readonly<T>
用于将所有传入的属性转变成只读项
1 | type Readonly<T> = { readonly [P in keyof T]: T[P] }; |
举个🌰:
1 | interface Todo { |
5、Record
Record<K, T>
用于将 K 中所有的属性的值转化为 T 类型
1 | type Record<K extends keyof any, T> = { [P in K]: T }; |
举个🌰:
1 | type subjects = 'ts' | 'js'; |
6、Pick
Pick<T, K>
用于从 T 中取出一系列 K 的属性
1 | type Pick<T, K extends keyof T> = { [P in K]: T[P] }; |
举个🌰:
1 | interface Sup { |
7、Exclude
在 TypeScript 2.8 中引入了一个条件类型, 示例如下:
1 | T extends U ? X : Y |
以上语句的意思就是 如果 T 是 U 的子类型的话,那么就会返回 X,否则返回 Y
条件类型甚至可以组合多个,举个🌰:
1 | type TypeName<T> = |
对于联合类型来说会自动分发条件,例如 T extends U ? X : Y
,T
可能是 A | B
的联合类型, 那实际情况就变成 (A extends U ? X : Y) | (B extends U ? X : Y)
。
有了以上的了解我们再来了解工具泛型 Exclude,Exclude<T, U>
的作用是从 T 中找出 U 中没有的元素, 换种更加贴近语义的说法其实就是从 T 中排除 U
1 | type Exclude<T, U> = T extends U ? never : T; |
举个🌰:
1 | type T = Exclude<1 | 2, 1 | 3> // -> 2 |
8、Extract
与 Exclude 恰好相反,Extract<T, U>
的作用是提取出 T 包含在 U 中的元素, 换种更加贴近语义的说法就是从 T 中提取出 U
1 | type Extract<T, U> = T extends U ? T : never; |
举个🌰:
1 | type T = Exclude<1 | 2, 1 | 3> // -> 1 |
9、ReturnType、Parameters、InstanceType、ConstructorParameters、
在 2.8 版本中,TypeScript 内置了一些与 infer 有关的映射类型,当 infer 用于函数类型中,可用于参数位置 new (...args: infer P) => any;
和返回值位置 new (...args: any[]) => infer P;
。
因此就内置如下两个映射类型:
用于提取函数类型的返回值类型:
1 | type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any; |
用于提取函数中参数类型
1 | type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never; |
举个🌰:
1 | type return = ReturnType<() => string>; // string |
用于提取构造函数中参数(实例)类型:
一个构造函数可以使用new来实例化,因此它的类型通常表示如下:
1 | type Constructor = new (...args: any[]) => any; |
当 infer 用于构造函数类型中,可用于参数位置 new (...args: infer P) => any;
和返回值位置 new (...args: any[]) => infer P;
。
因此就内置如下两个映射类型:
1 | // 获取参数类型 |
举个🌰:
1 | class TestClass { |
10、NonNullable
NonNullable<T>
主要用于从 T 中剔除 null 和 undefined
1 | type NonNullable<T> = T extends null | undefined ? never : T; |
举个🌰:
1 | type T0 = NonNullable<string | number | undefined>; // string | number |
参考资料
极客时间《TypeScript开发实战》专栏
《深入理解TypeScript》