# ts_符号、关键字和类型工具
# 符号
!
非空断言,不推荐使用,告诉 TS 一个变量不是null
也不是undefined
。
# !
非空断言
console.log(obj!.a)
# 关键字
typeof
获取一个变量的类型keyof
获取一个对象中的所有 key 值in
遍历一个类型extends
在泛型中约束一个泛型应该继承自另一个类型infer
在条件类型语句中,可以用infer
声明一个类型变量并且对它进行使用。
# typeof
不同于 JS 中的 typeof
,TS 中的 typeof
可以获取一个值的类型,只获取属性名和属性类型,不会获取具体的值。
- JS 中的
typeof
是用于表达式中,用于运行时获取一个值的类型 - TS 中的
typeof
是用于类型上下文中,用于编译时推断一个值的类型
通常情况下,一个值是不能作为类型来使用的,使用 typeof
后则可以将一个值用于类型定义。typeof
之后只能是一个值,不能是一个类型。
const obj1 = {
name: 'tom',
age: 20,
}
const obj2: typeof obj1 = {
name: 'jerry',
age: 30,
}
2
3
4
5
6
7
8
9
typeof
通常用于具有结构的值,比如对象等。
如果将 typeof
用于原始类型的值,返回的类型将是这个值,通常这样做没有意义:
const var1 = 'hello'
const var2 = 1
const var3 = true
const var4 = { name: 'tom', age: 20 }
const var5 = [1, 2, 3]
type T1 = typeof var1 // 无意义,相当于 type T1 = 'hello'
type T2 = typeof var2 // 无意义,相当于 type T2 = 1
type T3 = typeof var3 // 无意义,相当于 type T3 = true
type T4 = typeof var4 // 相当于 type T4 = { name: string, age: number }
type T5 = typeof var5 // 相当于 type T5 = number[]
2
3
4
5
6
7
8
9
10
11
JS 中的 typeof
和 TS 中的 typeof
:
const tom = {
name: 'tom',
age: 20,
}
// 函数定义返回值时使用的 typeof, 使用的是 TS 中的 typeof
const fn = (name: string, age: number | string): typeof tom => {
// 表达式中的 typeof, 实际上是使用的 JS 中的 typeof
if (typeof age === 'number') {
return {
name,
age,
}
}
return {
name,
age: +age,
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# keyof
keyof
用于获取一个类型的所有 key 值,并返回这些 key 值(注意不是 key 值的类型)组成的联合类型。如果该类型具有字符串或数字索引签名,keyof
将返回这些类型。
keyof
之后只能是一个类型,不能是一个值。如果要获取一个值的所有类型,需要使用 keyof typeof <值>
interface IPerson {
name: string
age: number
}
// 相当于 const var1: 'name' | 'age' = 'age'
const var1: keyof IPerson = 'age'
const var2: IPerson = {
name: 'tom',
age: 20,
}
// keyof typeof 连用,相当于 const var3: 'name' | 'age' = 'name'
const var3: keyof typeof var2 = 'name'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
keyof
获取拥有 number
索引签名的类型:
type Type1 = {
[n: number]: unknown
}
type Type2 = keyof Type1; // 相当于 Type2 = number
const var1: Type2 = 1 // 相当于 const var1: number = 1
2
3
4
5
6
keyof
获取拥有 string
索引签名的类型:
type Type1 = {
[x: string]: unknown
}
type Type2 = keyof Type1; // 相当于 Type2 = string | number
const var1: Type2 = 1
const var2: Type2 = 'hi'
2
3
4
5
6
7
8
可以看到,上例中,Type1
只拥有 string
类型的索引签名,但 keyof
取出了 string | number
类型,这是因为,在 JavaScript 中,对象的 key 总是会被转为 string 类型,obj[0]
总是相当于 obj['0']
。
# in
遍历一个联合类型
type Keys = 'a' | 'b' | 'c'
// { a: any, b: any, c: any }
type Obj = {
[p in Keys]: any
}
2
3
4
5
6
7
# extends
extends
在 JS 中也存在,用于一个 class 继承另一个 class ,比如 class A extends B
。
在 TS 的类型系统中,A extends B
表示类型 A 是否是继承(扩展)自类型 B,也就是类型 A 可赋值给类型 B
TS 的类型系统中的 extends
通常用于泛型约束和类型条件表达式。
在泛型约束中,extends
用于将泛型变量的类型约束在一定的范围之内。比如:
// 将泛型变量 T 约束为只能为 number 或 string 之一
type Func = <T extends number | string>(x: T)=> T
2
在类型条件表达式中,语法为:
T extends U ? X : Y
示例:
interface Animal {
live(): void
}
interface Dog extends Animal {
woof(): void
}
// 结果为 type Example1 = number
type Example1 = Dog extends Animal ? number : string
// 结果为 type Example2 = string
type Example2 = RegExp extends Animal ? number : string
2
3
4
5
6
7
8
9
10
11
12
13
当泛型变量传入联合类型时,会有一些地方可能和想象结果不一样:
type ToArray1<Type> = Type extends any ? Type[] : never
type ToArray2<Type> = [Type] extends any ? Type[] : never
// type StrArrOrNumArr = string[] | number[]
type T1 = ToArray1<string | number>
// type T2 = (string | number)[]
type T2 = ToArray2<string | number>
2
3
4
5
6
7
8
可以看到,上面使用 []
将泛型包裹一下后,返回的类型实际是不一样的。
类型条件表达式在泛型中会非常有用:
// T['message'] 相当于在提取泛型 T 的 message 属性的类型,结果取决于传入的泛型
type MessageOf<T> = T extends { message: unknown } ? T['message'] : never
interface Email {
message: string
}
interface Phone {
message: number
}
interface Dog {
bark(): void
}
// 结果为:type EmailMessageContents = string
type EmailMessageContents = MessageOf<Email>
// 结果为:type PhoneMessageContents = number
type PhoneMessageContents = MessageOf<Phone>
// 结果为:type DogMessageContents = never
type DogMessageContents = MessageOf<Dog>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
在上面的例子中,使用了 T['message']
来提取传入的泛型 T 的 message
属性的类型。但这其实是手动通过索引 message
来提取的。
在条件类型表达式中,还提供了一个更方便的用于提取类型的关键字 infer
。它可以帮我们更方便地提取一个类型出来。
# infer
infer
意为推断,但依据它的作用,其实可以将其理解为「捕获」,infer X
语句的意思就相当于:捕获一个类型并将这个类型分配给类型变量 X
保存。
infer X
语句放置在哪里呢,放置在一个想要捕获类型的地方,也就是说将 infer X
语句当作一个占位语句放置在本来应该书写类型的地方,就可以捕获到这个类型。
比如数组的类型,对象属性类型,函数参数的类型,函数的返回值等
// 用变量 ItemType 捕获数组的类型
// 也可写为 Array<infer ItemType> ,就像 string[] 和 Array<string> 写法一样
type T1<T> = T extends (infer ItemType)[] ? ItemType : T
// 用变量 MsgType 捕获对象属性的类型
type T2<T> = T extends { message: infer MsgType } ? MsgType : T
// 用变量 ParamType 捕获函数的参数 x 的类型
type T3<T> = T extends (x: infer ParamType) => string ? ParamType : T
// 用变量 ReturnType 捕获函数的返回值的类型
type T4<T> = T extends (x: string) => infer ReturnType ? ReturnType : T
type T11 = T1<string[]> // type T11 = string
type T12 = T1<Array<number>> // type T12 = number
type T21 = T2<{ message: string }> // type T21 = string
type T22 = T2<{ message: number }> // type T22 = number
type T31 = T3<(x: string) => string> // type T31 = string
type T32 = T3<(x: number) => string> // type T32 = number
type T41 = T4<(x: string) => string> // type T41 = string
type T42 = T4<(x: string) => number> // type T42 = number
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
下面看一个注释更详细的示例:
// (infer ItemType)[] 在这里表示任何类型的数组,比如 string[], number[] 等
// infer ItemType 就是将 ItemType 当作一个类型变量,传入的是什么类型的数组,ItemType 就是什么类型。
type Flatten<T> = T extends (infer ItemType)[] ? ItemType : T
// infer ItemType 将类型 string 推断给了 ItemType
// 结果为 type T1 = string
type T1 = Flatten<string[]>
// infer ItemType 将类型 number 推断给了 ItemType
// 结果为:type T2 = number
type T2 = Flatten<number[]>
// 类型条件表达式走了 false 分支,直接返回了泛型变量的类型
// 结果为 type T3 = string
type T3 = Flatten<string>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
其实,TS 提供的多个类型工具都是使用的 infer
语句,比如很常用的 ReturnType
, 还有 ThisParameterType
, OmitThisParameter
, Parameters
, ConstructorParameters
, InstanceType
。
比如,我们经常使用 ReturnType<>
来获取一个函数的返回值类型
type Func1 = (x: number, y: number) => number
type Func2 = (x: number, y: number) => string
type T1 = ReturnType<Func1> // type T1 = number
type T2 = ReturnType<Func2> // type T2 = string
2
3
4
5
来看一下 ReturnType
是怎么使用 infer
来捕获我们传入的函数的返回值的:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type ReturnType<T extends (...args: any) => any>
定义了一个类型别名ReturnType
, 它有一个泛型变量T
,T
被约束为必须是一个函数。T extends (...args: any) => infer R ? R : any;
类型条件表达式,泛型变量T
如果继承自(...args: any) => infer R
, 则返回 通过infer
捕获到的函数返回值类型,否则返回any
类型。