工具类型是 TypeScript 内置的泛型类型,用来把一个类型转换成另一个类型。不必再写一个所有字段都可选的 User 类型,直接写 Partial<User>;不必复制三个字段,写 Pick<User, "id" | "name"> 即可。它们是全局的,不需要导入。
User 改变时,这四个派生类型会自动跟着变。TypeScript 标准库(lib.es5.d.ts)一共声明了 22 个工具类型。下面按作用的类型种类分组列出全部工具类型,有详细页面的附上了链接。
对象类型:Partial、Required、Readonly、Pick、Omit、Record
| 工具类型 | 作用 | 示例 |
|---|---|---|
Partial<T> | 让所有属性变成可选 | 用 Partial<User> 表示更新数据 |
Required<T> | 让所有属性变成必需(去掉 ?) | 应用默认值之后的 Required<Config> |
Readonly<T> | 让所有属性变成 readonly | Readonly<State> |
Pick<T, K> | 只保留键 K | Pick<User, "id" | "name"> |
Omit<T, K> | 去掉键 K | Omit<User, "password"> |
Record<K, V> | 键为 K、值为 V 的对象类型 | Record<"en" | "de", string> |
Required<T> 与 Partial<T> 正好相反;Partial 一页同时讲解了这两者,包括像这里这样的展开运算会放进来的显式 undefined。
联合类型:Exclude、Extract、NonNullable
| 工具类型 | 作用 | 示例 |
|---|---|---|
Exclude<U, M> | 去掉可以赋值给 M 的联合成员 | Exclude<"a" | "b" | "c", "a"> 得到 "b" | "c" |
Extract<U, M> | 保留可以赋值给 M 的联合成员 | Extract<string | number, number> 得到 number |
NonNullable<T> | 去掉 null 和 undefined | NonNullable<string | null> 得到 string |
这三个工具类型作用于联合类型,而不是对象。这是它们和 Pick、Omit 的关键区别:后两者接收的是一个对象类型和它的一组键。
函数和类:Parameters、ReturnType 等
| 工具类型 | 作用 | 示例 |
|---|---|---|
ReturnType<F> | 函数类型的返回类型 | ReturnType<typeof createStore> |
Parameters<F> | 以元组形式给出参数类型 | Parameters<typeof fetchPage>[0] |
ConstructorParameters<C> | 以元组形式给出类构造函数的参数 | ConstructorParameters<typeof Point> |
InstanceType<C> | 构造函数创建的实例类型 | InstanceType<typeof Point> |
ThisParameterType<F> | 函数 this 参数的类型 | ThisParameterType<typeof greet> |
OmitThisParameter<F> | 去掉 this 参数后的函数类型 | greet.bind(obj) 的类型 |
ThisType<T> | 设置对象字面量方法中 this 的类型 | 在构建器 API 中配合 noImplicitThis 使用 |
NoInfer<T> | 阻止从这个位置推断类型参数 | fallback: NoInfer<C> |
这里需要写 typeof createOrder,因为这些工具类型接收的是类型,而 createOrder 是一个值。类也一样:typeof Point 是构造函数的类型,而直接把 Point 当作类型使用时,它本身就表示实例类型。
NoInfer 控制泛型从哪里获得类型:
如果没有 NoInfer,TypeScript 会根据两个实参推断 C,并把它拓宽为 "red" | "green" | "blue",于是 fallback 中的拼写错误也会被接受。
字符串:Uppercase、Lowercase、Capitalize、Uncapitalize
| 工具类型 | 作用 | 示例 |
|---|---|---|
Uppercase<S> | 把字符串字面量类型转成大写 | Uppercase<"get"> 得到 "GET" |
Lowercase<S> | 转成小写 | Lowercase<"GET"> 得到 "get" |
Capitalize<S> | 首字母大写 | Capitalize<"name"> 得到 "Name" |
Uncapitalize<S> | 首字母小写 | Uncapitalize<"Name"> 得到 "name" |
这四个工具类型内置在编译器中,而不是用 TypeScript 写成的。它们在模板字面量类型中最有用,例如用 `on${Capitalize<E>}` 生成事件处理函数的名字。
Promise:Awaited
| 工具类型 | 作用 | 示例 |
|---|---|---|
Awaited<T> | await 得到的类型,会展开嵌套的 Promise | Awaited<Promise<Promise<number>>> 得到 number |
Awaited<ReturnType<typeof fn>> 是给异步函数的结果类型命名的标准写法,无需另外声明。
组合使用工具类型
工具类型可以嵌套。下面几种组合经常出现,值得熟记:
嵌套的工具类型要从里往外读:Readonly<Pick<Post, "id" | "title">> 先保留两个属性,再把它们设为只读。用同样的组件还能写出只让部分键变成可选的 PartialBy 辅助类型;Partial 一页给出了完整写法。
工具类型在运行时不起任何作用
所有工具类型在代码编译时都会被擦除。类型为 Omit<User, "password"> 的值,如果来源对象带有 password,运行时它依然带着:
类型只限制你的代码能读取什么。要从数据中删掉一个字段,就像最后几行那样用解构把它去掉;要在运行时阻止修改,用 Object.freeze,而不是 Readonly。内置工具类型都是一行写成的映射类型和条件类型,所以你也可以用同样的工具编写自己的工具类型。
常见问题
TypeScript 中的工具类型是什么?
TypeScript 自带的一组泛型类型,用来把一个类型转换成另一个类型:Partial<T> 让所有属性变成可选,Pick<T, K> 只保留部分属性,ReturnType<F> 取得函数的返回类型,等等。它们声明在标准库里,所以无需导入就能使用。
使用工具类型需要导入吗?
不需要。Partial、Omit、Record、ReturnType 等都是 TypeScript 内置库文件中的全局类型。在任何地方直接写 Partial<User> 即可,不需要 import,也不需要安装 npm 包。
TypeScript 内置了哪些工具类型?
共 22 个,都声明在 lib.es5.d.ts 中:Partial、Required、Readonly、Pick、Omit、Record、Exclude、Extract、NonNullable、Parameters、ConstructorParameters、ReturnType、InstanceType、ThisParameterType、OmitThisParameter、ThisType、NoInfer、Awaited、Uppercase、Lowercase、Capitalize 和 Uncapitalize。
工具类型会在运行时修改对象吗?
不会。它们只描述类型,在编译出的 JavaScript 中会被擦除。Omit<User, "password"> 不会删除 password 属性,Readonly<T> 也不会冻结任何东西。要真正修改对象,需要写代码:用解构的剩余模式、Object.freeze 等。
可以自己编写工具类型吗?
可以。内置的工具类型就是普通的 TypeScript:大多数是 lib.es5.d.ts 中一行写成的映射类型或条件类型。type Nullable<T> = { [K in keyof T]: T[K] | null } 就是用同样方式写出的自定义工具类型。