Menu

TypeScript 类型别名:type 关键字用法与示例

类型别名用 type 关键字给任意类型起一个名字:对象形状、联合类型、元组、函数、泛型和递归类型都可以。本文讲解语法、每种形式的写法,以及为什么别名只是一个名字,而不是一个新的独立类型。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

类型别名就是给一个类型起名字。写下 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 }。当值先于类型存在时,这样可以只保留一个数据来源。

Coddy 编程语言插图

和 Coddy 一起学编程

开始