JavaScript 中的内置方法是用来操作值的,TypeScript 的内置方法是用来操作类型的。

Partial:可选属性

它接收一个对象类型,并将这个对象类型的所有属性都标记为可选,这样我们就不需要一个个将它们标记为可选属性了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type User = {
name: string
age: number
email: string
}

type PartialUser = Partial<User>

const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
}

// 可以不实现全部的属性了!
const partialUser: PartialUser = {
name: 'John Doe',
age: 30
}

从 Partial 的使用方式我们可以看到,工具类型使用起来就像一个函数——你给它入参,它还你出参,而出入参都是类型!而这些函数预留的参数,其实也正是我们此前介绍过的,类型世界中的变量——泛型。

Required:必选属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type User = {
name?: string
age?: number
email?: string
}

type RequiredUser = Required<User>

const user: User = {
name: 'John Doe'
}

// 现在你必须全部实现这些属性了
const requiredUser: RequiredUser = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
}

Readonly:只读属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type User = {
name: string
age: number
email: string
}

type ReadonlyUser = Readonly<User>

const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
}

const readonlyUser: ReadonlyUser = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
}

// 修改 user 对象的属性
user.name = 'Jane Doe'
user.age = 25
user.email = 'jane.doe@example.com'

// 修改 readonlyUser 对象的属性
// readonlyUser.name = 'Jane Doe'; // 报错
// readonlyUser.age = 25; // 报错
// readonlyUser.email = 'jane.doe@example.com'; // 报错

TypeScript 内置的工具类型中,并不包括与 Readonly 结对出现的版本,你可以认为,只读通常是一个不可逆的行为,如果能够随意将只读修饰移除,就可能破坏了只读带来的安全性。同时,不同于可选与必选,我们最开始获得的类型输入基本不会携带 readonly 修饰。

Record:基于索引签名类型

索引签名类型,用于声明一个内部属性键与键值类型一致的对象类型,而 TypeScript 中基于索引签名类型提供了一个简化版本 Record,它能够用更简洁的语法实现同样的效果:

1
2
3
4
5
6
7
8
9
10
type UserProps = 'name' | 'job' | 'email'

// 等价于你一个个实现这些属性了
type User = Record<UserProps, string>

const user: User = {
name: 'John Doe',
job: 'fe-developer',
email: 'john.doe@example.com'
}

可以使用 Record 类型来声明属性名还未确定的接口类型

1
2
3
4
5
6
7
8
9
10
type User = Record<string, string>

const user: User = {
name: 'John Doe',
job: 'fe-developer',
email: 'john.doe@example.com',
bio: 'Make more interesting things!',
type: 'vip'
// ...
}

Pick 类型

Pick 类型接收一个对象类型,以及一个字面量类型组成的联合类型,这个联合类型只能是由对象类型的属性名组成的。它会对这个对象类型进行裁剪,只保留你传入的属性名组成的部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type User = {
name: string
age: number
email: string
phone: string
}

// 只提取其中的 name 与 age 信息
type UserBasicInfo = Pick<User, 'name' | 'age'>

const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com',
phone: '1234567890'
}

const userBasicInfo: UserBasicInfo = {
name: 'John Doe',
age: 30
}

Omit 类型

它的入参和 Pick 类型一致,但效果却是相反的——它会移除传入的属性名的部分,只保留剩下的部分作为新的对象类型.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type User = {
name: string
age: number
email: string
phone: string
}

// 只移除 phone 属性
type UserWithoutPhone = Omit<User, 'phone'>

const user: User = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com',
phone: '1234567890'
}

const userWithoutPhone: UserWithoutPhone = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
}

Pick 与 Omit 类型是类型编程中相当重要的一个部分,举例来说,我们可以先声明一个代表全局所有状态的大型接口类型:

1
2
3
4
5
6
7
8
9
10
11
12
type User = {
name: string
age: number
email: string
phone: string
address: string
gender: string
occupation: string
education: string
hobby: string
bio: string
}

然后在我们的子组件中,可能只用到了其中一部分的类型,此时就可以使用 Pick 类型将我们需要的部分择出来:

1
2
3
4
5
6
7
type UserBasicInfo = Pick<User, 'name' | 'age' | 'email'>

const userBasicInfo: UserBasicInfo = {
name: 'John Doe',
age: 30,
email: 'john.doe@example.com'
}

反之,如果我们用到了大部分类型,只有数个类型需要移除,就可以使用 Omit 类型来减少一些代码量:

1
2
3
4
5
6
7
8
9
10
11
type UserDetailedInfo = Omit<User, 'name' | 'age' | 'email'>

const userDetailedInfo: UserDetailedInfo = {
phone: '1234567890',
address: '123 Main St',
gender: 'male',
occupation: 'developer',
education: 'Bachelor',
hobby: 'reading',
bio: 'A passionate developer'
}

集合类型:Exclude、Extract

Exclude 类型,它能够从一个类型中移除另一个类型中也存在的部分,即:差集,Extract 类型用于提取另一个类型中也存在的部分,即交集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address'
type RequiredUserProps = 'name' | 'email'

// OptionalUserProps = UserProps - RequiredUserProps
type OptionalUserProps = Exclude<UserProps, RequiredUserProps>

const optionalUserProps: OptionalUserProps = 'age' | 'phone' | 'address'

type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address'
type RequiredUserProps = 'name' | 'email'

type RequiredUserPropsOnly = Extract<UserProps, RequiredUserProps>

const requiredUserPropsOnly: RequiredUserPropsOnly = 'name' | 'email'

函数类型

对于函数类型,工具类型能起到什么作用?
函数类型=参数类型+返回值类型,这个定律适用于所有的函数类型定义。而我们一般又不会去修改参数与返回值位置的类型,那就只剩下读取了。
内置工具类型中提供了 Parameters 和 ReturnType 这两个类型来提取函数的参数类型与返回值类型:

1
2
3
4
5
6
7
type Add = (x: number, y: number) => number

type AddParams = Parameters<Add> // [number, number] 类型
type AddResult = ReturnType<Add> // number 类型

const addParams: AddParams = [1, 2]
const addResult: AddResult = 3

如果我们只有一个函数,而并没有这个函数类型呢?此时可以使用 TypeScript 提供的类型查询操作符,即 typeof(和 JavaScript 的 typeof 区分一下),来获得一个函数的结构化类型,再配合工具类型即可即可:

1
2
3
4
5
6
7
8
9
const addHandler = (x: number, y: number) => x + y

type Add = typeof addHandler // (x: number, y: number) => number;

type AddParams = Parameters<Add> // [number, number] 类型
type AddResult = ReturnType<Add> // number 类型

const addParams: AddParams = [1, 2]
const addResult: AddResult = 3

对于异步函数类型,提取出的返回值类型是一个 Promise 这样的类型,如果我想提取 Promise 内部的 string 类型呢?TypeScript 准备了 Awaited 类型用于解决这样的问题:

1
2
3
4
5
6
7
8
const promise = new Promise<string>((resolve) => {
setTimeout(() => {
resolve('Hello, World!')
}, 1000)
})

type PromiseInput = Promise<string>
type AwaitedPromiseInput = Awaited<PromiseInput> // string

可以直接嵌套在 ReturnType 内部使用:

1
2
3
4
5
6
7
8
9
10
// 定义一个函数,该函数返回一个 Promise 对象
async function getPromise() {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve('Hello, World!')
}, 1000)
})
}

type Result = Awaited<ReturnType<typeof getPromise>> // string 类型