类型别名就是给一个类型起名字。写下 type Name = ... 之后,凡是需要类型的地方都可以使用 Name。和接口不同,别名可以命名任何类型,而不只是对象形状。
别名只存在于编译期。编译出的 JavaScript 中不会有任何一行 type。
语法
type Name = SomeType;
type Name<T> = SomeTypeUsing<T>; // generic
export type Name = SomeType; // exported from a module
按照惯例,名字使用 PascalCase。等号右边可以是任何类型表达式,别名也可以使用其他别名,包括它自己(见下文的递归类型)。别名在声明它的整个作用域内都可用,甚至在声明那一行之前也可以,因为类型是在编译期检查的,不会像 const 或 let 那样按顺序执行。
对象类型
描述对象形状时,别名和接口的写法几乎一样。别名使用 =,通常以分号结尾。
别名不能使用 extends,交叉类型 & 承担了这个角色。多余属性检查、可选属性和只读属性的行为与接口完全相同。描述对象形状时该选哪一个,在 interface 与 type 的对比页面中有讲解。
联合类型、元组和函数
这些是只有别名才能命名的情况,也是 type 存在的主要原因。联合类型别名列出所有可选类型,元组别名固定数组的长度和元素类型,函数别名描述一个函数签名。
在 scale 中,参数不需要标注类型:Transform 别名已经提供了它们的类型。元组语法在 元组 中讲解,联合类型的收窄在联合类型页面中讲解。
泛型类型别名
类型参数让一个别名适用于多种类型。和函数参数一样,类型参数也可以有默认值。
Result<number> 使用了默认值 E = string。在实际代码中,type Nullable<T> = T | null 或 type Dict<T> = Record<string, T> 这样的小型泛型别名很常见。内置的工具类型(Partial<T>、Pick<T, K>、ReturnType<F>)也是泛型别名,声明在标准库中。
别名只是名字,不是新类型
别名不会创建一个独立的类型。它只是右边那个类型的第二个名字,编译器把两者视为完全相同。两个 string 的别名可以完全互换:
这段代码输出 cancelling u_42,没有任何错误,这正是别名无法防止的 bug。别名对阅读代码的人仍有帮助(类型为 OrderId 的参数比 string 表达得更多),但如果混用两种 id 必须是编译错误,就要使用品牌类型,例如 string & { readonly __brand: "OrderId" }。
递归类型别名
别名可以引用自身,树、嵌套列表和 JSON 就是这样描述的:
Json 别名会拒绝 JSON 无法表示的值:把 { when: new Date() } 或 { f: undefined } 赋给 Json 都是编译错误(TS2322)。
从值得到类型:typeof
如果值已经存在,可以直接拿到它的类型并命名,而不必手写一遍。typeof 类型运算符读取一个变量的类型,还可以和其他运算符组合使用:
const defaults = { retries: 3, verbose: false, level: "info" };
type Options = typeof defaults;
// { retries: number; verbose: boolean; level: string }
type OptionKey = keyof typeof defaults;
// "retries" | "verbose" | "level"
function start(port: number) {
return { port, startedAt: new Date() };
}
type Server = ReturnType<typeof start>;
// { port: number; startedAt: Date }
值始终是唯一的数据来源:给 defaults 添加一个属性,Options 也会跟着变。更多内容见 typeof。
导出和导入别名
别名的导出和导入方式与值相同。import type 明确表示只导入类型,所以这条导入语句总会从 JavaScript 输出中移除:
// shapes.ts
export type Point = { x: number; y: number };
export type Shape = { kind: "circle"; center: Point; radius: number };
// main.ts
import type { Point, Shape } from "./shapes.js";
const p: Point = { x: 1, y: 2 };
常见问题
TypeScript 中的类型别名是什么?
类型别名是一个类型的名字,用 type 关键字声明:type Point = { x: number; y: number }。之后,凡是需要类型的地方都可以使用 Point。它可以命名任何类型:对象形状、联合类型、元组、函数签名、原始类型或泛型类型。
类型别名会创建一个新类型吗?
不会。别名只是已有类型的另一个名字。有了 type UserId = string 和 type OrderId = string,需要 OrderId 的地方可以传入 UserId,因为两者都只是 string。想要不能互换的类型,请使用品牌类型(branded type)。
类型别名可以引用自身吗?
可以,只要自引用嵌套在对象类型、数组或元组中:type TreeNode = { value: number; children: TreeNode[] },以及包含 Json[] 的 Json 别名,都能正常工作。像 type Loop = Loop | string 这样直接引用自身会报 TS2456 错误:Type alias 'Loop' circularly references itself。
类型别名可以是泛型的吗?
可以。在名字后面写类型参数:type Box<T> = { value: T },然后像 Box<number> 这样使用。类型参数可以有默认值(type Result<T, E = string> = ...)和约束(type Keys<T extends object> = keyof T)。
如何从已有对象得到它的类型?
使用 typeof 类型运算符:const defaults = { retries: 3, verbose: false }; type Options = typeof defaults; 得到 { retries: number; verbose: boolean }。当值先于类型存在时,这样可以只保留一个数据来源。