模板字面量类型
模板字面量类型
一句话概括
模板字面量类型 = 类型空间的字符串拼接——${Prefix}${Name} 能在类型层面拼字符串、做模式匹配、解析路由,TypeScript 4.1 后类型系统才有了”字符串运算”的能力。
核心知识点
1. 基础:类型空间的 ${}
1
2
3
4
5
6
7
8
9
type EventName = "click" | "focus" | "blur";
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
// 联合类型占位符 → 自动笛卡尔积展开
type Lang = "zh" | "en";
type Region = "CN" | "US";
type Locale = `${Lang}-${Region}`;
// "zh-CN" | "zh-US" | "en-CN" | "en-US"
2. 四个内置字符串工具类型
1
2
3
4
5
6
type A = Uppercase<"hello">; // "HELLO"
type B = Lowercase<"HELLO">; // "hello"
type C = Capitalize<"hello">; // "Hello"
type D = Uncapitalize<"Hello">; // "hello"
// 注意:这些是 TS 内置的编译器层面的转换,不是运行时的 .toUpperCase()
3. infer + 模板字面量 = 模式匹配
1
2
3
4
5
6
7
// 提取特定前缀后的路径
type ExtractPath<T extends string> = T extends `/api/${infer P}` ? P : never;
type P = ExtractPath<"/api/users">; // "users"
// 提取域名:匹配 https:// 到第一个 / 之间的部分
type Host<T extends string> = T extends `https://${infer H}/${string}` ? H : never;
type H = Host<"https://api.github.com/v3">; // "api.github.com"
4. 递归实现字符串工具
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// KebabCase:HelloWorld → hello-world
type KebabCase<S extends string> =
S extends `${infer First}${infer Rest}`
? Rest extends Uncapitalize<Rest>
? `${Lowercase<First>}${KebabCase<Rest>}`
: `${Lowercase<First>}-${KebabCase<Rest>}`
: S;
type K = KebabCase<"HelloWorld">; // "hello-world"
// Split:按分隔符拆成联合类型
type Split<S extends string, D extends string> =
S extends `${infer Head}${D}${infer Tail}`
? Head | Split<Tail, D>
: S;
type Parts = Split<"a,b,c", ",">; // "a" | "b" | "c"
5. 实战:路由参数提取
1
2
3
4
5
6
7
8
9
10
// 从路由模式提取参数对象类型
type RouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & RouteParams<`/${Rest}`>
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
type Params = RouteParams<"/user/:id/post/:postId">;
// { id: string } & { postId: string }
「其实你每天都在用」
- Redux action type 生成:
${FETCH}_${USER}_${REQUEST}类型的笛卡尔积完美建模 - CSS 属性值约束:
type Size = \${number}px` | `${number}rem`` - 国际化 key 类型:
`${Section}.${Key}`防止拼错翻译 key - 事件名约束:
`on${EventName}`确保不会写出onHover这种不存在的处理函数 - 路由路径参数解析:react-router / vue-router 的类型推导底层就用到了模板字面量类型
常见误解(FAQ)
❌ 误区 1:${string} 匹配所有字符串,所以跟 string 一样
${string} 可以出现在模板中作为”通配符”,但最终结果仍是模板字面量类型,不是 string。比如 /api/${string} 匹配 /api/users 但不匹配 /api 或 /users/api。
❌ 误区 2:模板字面量里的联合类型总是安全的
2-3 个小联合类型组合没问题。但 5 个各含 20 个成员的联合类型拼在一起 = 3,200,000 个成员,超出 TS 编译器的约 10 万成员限制,会报 union type too complex。
❌ 误区 3:递归模板字面量随便写
递归深度有约 50 层限制。处理很长的字符串(如 100 个字符的 URL)时递归可能超过限制。日常场景(如路由解析)完全够用。
❌ 误区 4:模板字面量类型跟 JS 模板字符串是同一个东西
一个是类型空间,一个是值空间。写法一样(都是反引号 + ${}),但前者只存在于编译时,后者存在于运行时。类型空间的 Uppercase<"hello"> 不会转成 JS 代码。
一句话总结
模板字面量类型让 TypeScript 的类型系统具备了”字符串计算”能力——从此路由参数、国际化 key、CSS 单位都能在编译期被精确约束,而不再是靠运行时正则 + 祈祷。
本文由作者按照 CC BY 4.0 进行授权