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.name = 'Jane Doe' user.age = 25 user.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 }
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 }
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 类型用于提取另一个类型中也存在的部分,即交集。
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'
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> type AddResult = ReturnType<Add>
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
type AddParams = Parameters<Add> type AddResult = ReturnType<Add>
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
| async function getPromise() { return new Promise<string>((resolve) => { setTimeout(() => { resolve('Hello, World!') }, 1000) }) }
type Result = Awaited<ReturnType<typeof getPromise>> // string 类型
|