TypeScript 模板字面量类型能解决什么问题?
简化版
模板字面量类型可以在类型层面拼接字符串字面量,用来表达事件名、接口路径、CSS 变量名、状态 key 等有规律的字符串集合。
它常和联合类型、映射类型、条件类型配合,把一组基础字段转换成更具体的字符串类型,从而减少手写字符串和拼写错误。
详细版
type EventName = "click" | "change";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onChange"
用字段生成事件:
type Model = {
name: string;
age: number;
};
type ChangeEvent = `${keyof Model & string}Changed`;
// "nameChanged" | "ageChanged"
模板字面量类型不是运行时代码,它只在编译期约束字符串形状。字符串组合过多时会产生笛卡尔积,类型数量可能快速膨胀。
完整版教学
一、它解决的是“有规律字符串”的类型表达
很多前端字符串不是随便写的,而是有命名规则。
field name -> nameChanged
field age -> ageChanged
模板字面量类型让这些规则在类型系统里表达出来。
它的价值不是炫技,而是把字符串约定从文档搬进编译器。
二、基础语法和运行时模板字符串相似
type Size = "sm" | "md" | "lg";
type ClassName = `button-${Size}`;
得到:
| 基础类型 | 结果 |
|---|---|
"sm" | "button-sm" |
"md" | "button-md" |
"lg" | "button-lg" |
这只发生在类型层,不会生成任何 JavaScript。
三、联合类型会展开成组合
type Method = "get" | "post";
type Resource = "User" | "Order";
type ApiName = `${Method}${Resource}`;
// "getUser" | "getOrder" | "postUser" | "postOrder"
如果两个联合分别有 10 个成员,组合后就是 100 个字符串。数量很大时要克制。
四、常和内置字符串工具类型配合
TypeScript 提供了 Uppercase、Lowercase、Capitalize、Uncapitalize。
type Field = "name" | "email";
type Setter = `set${Capitalize<Field>}`;
// "setName" | "setEmail"
这类组合适合生成事件名、setter 名、组件 prop 名等。
五、和映射类型配合生成 API
type Watchers<T> = {
[K in keyof T & string as `${K}Changed`]: (value: T[K]) => void;
};
type UserWatchers = Watchers<{
name: string;
age: number;
}>;
这里 as 做键名重映射,模板字面量类型负责生成新键。
六、不要滥用到不可维护
模板字面量类型适合规则稳定的字符串集合,不适合把复杂业务协议全部写成类型体操。
type Path = `/api/${"user" | "order"}/${"list" | "detail"}`;
如果路径规则来自后端 OpenAPI,自动生成类型通常比手写复杂模板更可靠。
七、常见误区与追问
- 误区:模板字面量类型会在运行时拼字符串。 它只存在于类型检查阶段。
- 误区:模板字面量类型只能拼接两个字符串。 它可以组合多个占位类型。
- 误区:联合组合没有成本。 多个大联合会产生大量组合类型。
- 误区:它可以校验任意复杂字符串格式。 复杂格式更适合运行时校验或代码生成。
- 追问:为什么要写
keyof T & string? 因为对象键还可能是 number 或 symbol,模板字符串需要字符串类 key。 - 追问:它和映射类型怎么配合? 通过键名重映射
as生成新属性名。
八、加强记忆
记忆链路:
union -> template literal -> new string union
keyof -> remap key -> typed API surface
回答时先讲有规律字符串,再给事件名或 setter 名例子,最后提醒联合笛卡尔积和运行时校验边界。