泛型
泛型通过一对尖括号来表示(<>
),尖括号内的字符被称为类型变量,这个变量用来表示类型。
1 | interface GenericType<T> { |
高级类型
交叉类型(&)
交叉类型说简单点就是将多个类型合并成一个类型,其语法规则和逻辑 “与” 的符号一致。这意味着你可以将给定的类型 A 与类型 B 或更多类型合并,并获得具有所有属性的单个类型。
1 | T & U |
1 | type LeftType = { |
联合类型(|)
联合类型的语法规则和逻辑 “或” 的符号一致,表示其类型为连接的多个类型中的任意一个。联合类型使你可以赋予同一个变量不同的类型。
1 | T | U |
1 | type UnionType = string | number |
类型别名(type)
前面提到的交叉类型与联合类型如果有多个地方需要使用,就需要通过类型别名的方式,给这两种类型声明一个别名。类型别名与声明变量的语法类似,只需要把 const
、let
换成 type
关键字即可。
1 | type Alias = T | U |
type
和interface
的相同点与不同点
都可以描述一个对象或者函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void
}
type User = {
name: string
age: number
}
type SetUser = (name: string, age: number): void
都可以实现类型的拓展,但是语法不相同
1
2
3
4
5
6
7
8
9
10
11
12
13interface Name {
name: string
}
interface User extends Name {
age: number
}
type Name = {
name: string
}
type User = Name & { age: number }type
可以声明基本类型别名,联合类型,元组等类型,但是interface
不行当有重名的
interface
声明时,ts 会自动合并声明,但是type
不行
类型索引(keyof)
keyof
类似于 Object.keys
,用于获取一个接口中 Key 的联合类型。
1 | interface Button { |
类型约束(extends)
这里的 extends
关键词不同于在 class 后使用 extends
的继承作用,泛型内使用的主要作用是对泛型加以约束。
1 | type BaseType = string | number | boolean |
extends
经常与 keyof
一起使用,例如我们有一个方法专门用来获取对象的值,但是这个对象并不确定,我们就可以使用 extends
和 keyof
进行约束。
1 | function getValue<T, K extends keyof T>(obj: T, key: K) { |
类型映射(in)
in
关键词的作用主要是做类型的映射,遍历已有接口的 key 或者是遍历联合类型。下面使用内置的泛型接口 Readonly
来举例。
1 | type Readonly<T> = { |
P in keyof T
相当于P in 'a' | 'b'
,相当于执行了一次 forEach 的逻辑
最后等同的效果为:
1 | interface ReadOnlyObj { |
条件类型(U ? X : Y)
条件类型的语法规则和三元表达式一致,经常用于一些类型不确定的情况。
1 | T extends U ? X : Y |
工具泛型
Partial
用于将一个接口的所有属性设置为可选状态
1 | type Partial<T> = { |
Required
与 Partial
相反,就是将接口中所有可选的属性改为必须的。
1 | type Required<T> = { |
Readonly
会转换类型的所有属性,以使它们无法被修改
1 | interface ReadonlyType { |
Record
此工具可帮助你构造具有给定类型T
的一组属性K
的类型。将一个类型的属性映射到另一个类型的属性时,Record
非常方便。
1 | interface EmployeeType { |
Pick
用于提取接口的某几个属性
1 | type Pick<T, K extends keyof T> = { |
1 | interface Todo { |
Omit
Omit
的作用与Pick
类型正好相反。 不是选择元素,而是从类型T
中删除K
个属性。
1 | type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>> |
1 | interface Todo { |
Extract
Extract
允许你通过选择两种不同类型中的共有属性来构造新的类型。也就是从T
中提取所有可分配给U
的属性。
1 | Extract<T, U> |
1 | interface FirstType { |
Exclude
与Extract
不同,Exclude
通过排除两个不同类型中已经存在的共有属性来构造新的类型。 它会从T
中排除所有可分配给U
的字段。
1 | Exclude<T, U> |
1 | interface FirstType { |
NonNullable
从T
中剔除null
和undefined
1 | NonNullable<T> |
1 | type NonNullableType = string | number | null | undefined |
infer 关键字
infer
这个词的含义即 推断,实际作用可以用四个字概括:类型推导。它会在类型未推导时进行占位,等到真正推导成功后,它能准确地返回正确的类型。
举个例子,我们在解包数组里的数据类型时,如果不使用infer
,通常是按以下方式去做的:
1 | type Ids = number[] |
但是如果使用infer
则会变得非常简单
1 | type Unpacked<T> = T extends (infer R)[] ? R : T |
在 TypeScript 中,对象、类、数组和函数的返回值类型都是协变关系,而函数的参数类型是逆变关系,所以 infer
位置如果在函数参数上,就会遵循逆变原则。
当
infer
在协变的位置上时,同一类型变量的多个候选类型将会被推断为联合类型,当
infer
在逆变的位置上时,同一类型变量的多个候选类型将会被推断为交叉类型。
1 | type Foo<T> = T extends { a: infer U; b: infer U } ? U : never |
1 | type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never |
never 类型
就像在数学中我们使用零来表示没有的数量一样,我们需要一个类型来表示类型系统中的不可能。
限制函数参数
由于我们永远无法赋值给一个 never
类型,因此我们可以使用它来对各种用例的函数施加限制。
1 | interface Foo { |
以上面的代码为例子,注意在 default
里面我们把被收窄为 never
的 val 赋值给一个显式声明为 never
的变量。如果一切逻辑正确,那么这里应该能够编译通过。
但是假如后来有一天你的同事改了 All 的类型:type All = Foo | Bar | Baz
然而他忘记了在 handleValue
里面加上针对 Baz 的处理逻辑,这个时候在 default branch 里面 val 会被收窄为 Baz,导致无法赋值给 never
,产生一个编译错误。所以通过这个办法,你可以确保 handleValue
总是穷尽 所有 All 的可能类型。
表示理论上无法到达的分支
1 | type A = 'foo' |
从联合类型中过滤出联合属性
1 | type Foo = { |
相当于type ExtractedType = Foo | never
而联合属性never
会过滤掉never
过滤映射类型
1 | type Filter<Obj extends Object, ValueType> = { |
当我们有条件地将映射类型中的key 重新映射 到never
时,这些 key 就会被过滤掉。