1. 技术难点:字符串也要有精确类型
普通 TypeScript 里字符串类型就是 string,但它太"宽"------表达不了"必须以某前缀开头""必须是指定的某种格式"这类约束:
- 接口路径没法约束 :
/api/users/:id,你希望get('/api/users/123')合法、get('/api/xxx/123')报错。 - 事件名/命令名拼写 :
'onClick'写成'onclick'或'on-click'不会报错。 - 从联合类型派生字符串 :根据已有类型生成
'${login}_token'、'${key}_enabled'这种键。
核心难点 :如何让字符串类型的形状可被描述和推导------既能校验输入是否符合格式,又能从类型派生新的字符串类型。模板字面量类型(Template Literal Types)正是为此而生,它让类型系统拥有了类似"字符串正则/模式匹配"的能力。
2. 完整解法:模板字面量类型的四个核心用法
2.1 基本:字符串模式(类似"类型正则")
用反引号模板语法定义字符串的匹配模式 ,${T} 可嵌入其他类型:
ts
// 只允许形如 "GET /api/users" 的字符串
type Route = `${'GET' | 'POST'} /${string}`;
const r1: Route = 'GET /api/users'; // ✅
const r2: Route = 'POST /api/users'; // ✅
// const r3: Route = 'DELETE /api/users'; // ❌ DELETE 不在联合里
// 带数字参数
type OrderId = `order_${number}`;
const o1: OrderId = 'order_42'; // ✅
// const o2: OrderId = 'order_abc'; // ❌ 数字匹配不上
直观理解 :${string} 匹配任意字符串,${number} 匹配数字,${'a'|'b'} 匹配指字面量------像正则里的 .*、\d、a|b。
2.2 字符串操作工具:从类型层面操作字符串
模板字面量常和字符串操作类型 组合(Uppercase/Lowercase/Capitalize/Uncapitalize),批量改造键名:
ts
// 把对象的键转大写
type UppercaseKeys<T> = { [K in keyof T as Uppercase<K & string>]: T[K] };
interface User { id: number; name: string; }
type UpperUser = UppercaseKeys<User>;
// type UpperUser = { ID: number; NAME: string; }
这里的关键是 as 子句 :映射类型里用 as 可以改键名,结合模板字面量/大写转换实现"键名变换"。
2.3 占位符推导 + infer:做"字符串解析"
模板字面量配合 infer 可以从字符串里提取部分类型(类似正则捕获组):
ts
// 提取 "GET /api" 中的方法部分
type ExtractMethod<S extends string> = S extends `${infer M} /${string}` ? M : never;
type M1 = ExtractMethod<'GET /api'>; // type M1 = 'GET'
type M2 = ExtractMethod<'POST /x'>; // type M2 = 'POST'
应用场景:解析路由、事件名、命名枚举的前缀,从字符串反推结构。
2.4 实战:强类型"事件总线 / 路由表"
模板字面量最实用的场景------用字符串联合驱动类型,让 API/事件/键名类型联动:
ts
// 1. 路由参数强类型: /api/users/5 → userId 是 number
type UserRoute = `/api/users/${number}`;
function getUser(route: UserRoute) { /* ... */ }
getUser('/api/users/123'); // ✅
// getUser('/api/users/abc'); // ❌ 编译期拦截
// 2. 事件名 + 负载联动
type EventMap = {
'user:login': { userId: number; token: string };
'user:logout': { userId: number };
};
// 监听时能自动推导对应该事件的负载类型
type EventName = keyof EventMap;
function on<E extends EventName>(event: E, cb: (payload: EventMap[E]) => void) {}
on('user:login', (p) => { p.token; }); // p 自动是 {userId, token}
3. 应用场景
| 场景 | 模板字面量类型价值 |
|---|---|
| 路由/API 路径 | 约束路径格式,路径参数类型化 |
| 事件总线/命令 | 事件名拼写检测 + 事件负载联动 |
| 键名批量变换 | 前缀/大小写转换、生成派生键 |
| 枚举派生 | 从联合生成 ${x}_${y} 组合 |
| 字符串解析 | 从文件名/路径提取结构化类型 |
解决的具体问题:
- 路由/命令/事件的类型拼写错误
- 需要从前缀/格式校验字符串
- 从已有关键字派生新键,让类型跟随业务命名规则
4. 要点总结
- 模板字面量类型 = 用
${T}把一个字符串类型定义成"模式",像类型层面的正则。 ${string}/${number}/${'a'|'b'}是三种基础占位,匹配对应类型。- 字符串操作类型 (
Uppercase/Lowercase等)+ 映射类型as子句,实现键名批量变换。 - 配合
infer能从字符串里提取子类型(解析方法名/前缀/路径参数)。 - 应用落地:路由参数、事件总线、键名派生、命令规范,让业务命名规则被类型"编码"进去。
- 高级用法涉及条件类型 + infer 组合,可构造复杂字符串 DSL,但别过度------够用即可。
一句话:模板字面量类型让 TS 的字符串类型从"宽泛的 string"变成"可匹配、可推导、可变形的模式",是构建强类型路由、事件、命名规则的核心工具。