Menu

TypeScript 模板字面量类型:语法与示例

模板字面量类型用和 JavaScript 模板字符串相同的反引号语法来构建字符串字面量类型:on${Capitalize<E>}。本文讲解它的语法、联合类型如何相乘、Uppercase 与 Capitalize、${number}px 这类模式、映射类型生成的 getter,以及用 infer 解析字符串。

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

模板字面量类型用和 JavaScript 模板字符串相同的反引号语法来构建字符串字面量类型。`on${Capitalize<"click" | "focus">}` 就是类型 "onClick" | "onFocus",由编译器计算得出:

模板字面量类型只存在于编译时。它们在你写代码时检查字符串字面量和带类型的值,不会给输出的 JavaScript 增加任何内容。上面的处理函数表用 Record 来要求每个名字都对应一个函数。

语法

在反引号里写字面文本和 ${...} 占位符。占位符里放的是类型而不是值:字符串、数字、bigint 或布尔字面量类型,它们的联合类型,或者宽泛类型 string、number、bigint、boolean、null 和 undefined 之一。

放入 string 或 number 这类宽泛类型的占位符会形成一种模式:类型保持为 `hello ${string}`,任何匹配的字符串都能被接受。放入有限联合类型(比如 boolean)的占位符则会展开为它的各个成员。

联合类型会相乘

有多个联合类型时,结果是所有的组合:

三种尺寸乘以两种色调,得到六个成员。数量增长得很快:五个占位符各放一个包含十个字母的联合类型,就会得到 100,000 个成员,TypeScript 会以 error TS2590: Expression produces a union type that is too complex to represent 拒绝。不需要每个确切值时,就用 ${string} 这样的宽泛占位符。

Uppercase、Lowercase、Capitalize、Uncapitalize

有四个内置类型可以改变字符串字面量类型的大小写。它们是内在类型(intrinsic):在编译器内部实现,而不是用 TypeScript 写成的。

类型输入结果
Uppercase<S>"hello world""HELLO WORLD"
Lowercase<S>"Content-Type""content-type"
Capitalize<S>"hello world""Hello world"
Uncapitalize<S>"UserName""userName"

它们只改变类型。要构建对应的运行时字符串,你仍然需要调用 toUpperCase(),或者自己截取字符串并把首字母大写,再告诉 TypeScript 结果的精确类型:

之所以需要 as,是因为 toUpperCase() 的返回类型是普通的 string。调用者看到的是函数签名,所以 capitalize("report") 的类型是字面量类型 "Report"。

字符串模式:${number}px 等

模式类型接受某种形状的所有字符串。它很适合 CSS 值,以及带有已知前缀的 id 和键:

${number} 接受 JavaScript 能读作数字的任何字符串,这比看上去要宽松:"-3px"、"1e3px" 和 "0x10px" 都能通过类型检查。请把这些模式当作防止字面量拼写错误的保护,而不是完整的校验。

模板字面量与映射类型

模板字面量类型最有用的地方是作为映射类型的 as 子句,用其他属性名生成新的属性名:

string & K 只保留字符串键,因为 Capitalize 不接受数字或 symbol。每个回调函数的参数类型都来自它所监听的那个属性。

用 infer 解析字符串

在条件类型中,模板字面量可以匹配一个字符串,并用 infer 捕获其中的部分。下面从路由模式中提取参数名:

调用时漏掉 postId,编译器就会报告缺少它。

模板表达式会被拓宽为 string

普通代码中的模板字符串表达式的类型是 string,即使它的每个部分都是字面量类型。加上 as const 就能保留模板字面量类型:

如果没有 as const,把 loose 赋值给 `log:${Level}` 会失败,因为 string 可能是任何内容。

常见问题

TypeScript 中的模板字面量类型是什么?

它是用反引号和 ${...} 占位符写成的字符串字面量类型,类似 JavaScript 模板字符串,只不过作用在类型层面。type Greeting = `hello ${string}` 接受任何以 hello 开头的字符串,而 `on${Capitalize<"click">}` 就是字面量类型 "onClick"。

在模板字面量类型中放入联合类型会发生什么?

模板会针对每个成员展开,有多个联合类型时会得到所有组合。`${"sm" | "lg"}-${"red" | "blue"}` 就是 "sm-red" | "sm-blue" | "lg-red" | "lg-blue"。组合数量过大时会报错 TS2590。

Uppercase、Lowercase、Capitalize 和 Uncapitalize 有什么作用?

它们是用来转换字符串字面量类型的内置类型:Uppercase<"id"> 是 "ID",Lowercase<"ID"> 是 "id",Capitalize<"name"> 是 "Name",Uncapitalize<"Name"> 是 "name"。它们只改变类型;要改变运行时的字符串,仍然需要调用 toUpperCase() 之类的方法。

模板字面量类型会在运行时校验字符串吗?

不会。和所有 TypeScript 类型一样,它们会被擦除,所以只在编译时检查字符串字面量和带类型的值。在运行时才到来的字符串(来自 JSON 或用户输入)只是 string,除非你用自己的代码去检查它。

为什么我的模板字符串被推断为 string,而不是字面量类型?

像 `on${event}` 这样的模板表达式赋值给变量时会被拓宽为 string。加上 as const(`on${event}` as const)或者为目标标注类型,TypeScript 就会保留模板字面量类型,例如 "onclick" | "onfocus"。

Coddy 编程语言插图

和 Coddy 一起学编程

开始