TypeScript 模板字面量
模板字面量分为两层:运行时用反引号做字符串插值,类型层面自 TypeScript 4.1 起可以用模板字面量类型在编译期推导字符串的形状。它能配合联合类型与 infer 实现强大的字符串解析,是类型体操的重要工具。
模板字符串回顾
反引号内用 ${} 嵌入表达式,属于运行时的字符串拼接:
const lang = "TypeScript";
const msg = `我正在学习 ${lang}`;
console.log(msg); // 输出:我正在学习 TypeScript
模板字面量类型
把普通模板字符串里的"值"换成"类型",就得到模板字面量类型:
type World = "world";
type Greeting = `Hello ${World}`;
const g: Greeting = "Hello world"; // 合法
// const bad: Greeting = "Hello ts"; // 编译错误:不是 "Hello world"
console.log(g); // 输出:Hello world
与联合类型自动展开
当占位符是联合类型时,TypeScript 会自动生成所有组合(笛卡尔积):
type Size = "small" | "large";
type Color = "red" | "blue";
type Banner = `${Size}-${Color}`;
// Banner = "small-red" | "small-blue" | "large-red" | "large-blue"
const b1: Banner = "large-blue"; // 合法
const b2: Banner = "mid-red"; // 编译错误
常用套路是把事件名批量转成回调名:
type EventName = "click" | "focus" | "scroll";
type HandlerName = `on${Capitalize<EventName>}`;
// HandlerName = "onClick" | "onFocus" | "onScroll"
用 infer 解析字符串
模板字面量类型与 infer 结合,可以在编译期"解构"字符串。下面提取文件名扩展名:
type Ext<T extends string> = T extends `${infer _}.${infer E}` ? E : never;
type A = Ext<"photo.png">; // "png"
type B = Ext<"archive.tar.gz">; // "gz"
type C = Ext<"README">; // never,没有扩展名
再比如提取路由参数:
type Param<T extends string> = T extends `/user/${infer P}` ? P : never;
type P1 = Param<"/user/1001">; // "1001"
type P2 = Param<"/login">; // never,路径格式不匹配
实际应用
模板字面量类型特别适合拼接口径一致的字符串:CSS 类名前缀、HTTP 路径、状态常量等。它把"拼错字符串"这类错误提前到编译期,接口越规范收益越大。
小结
模板字符串负责运行时的插值,模板字面量类型负责编译期的字符串校验与推导。掌握它与联合类型、infer 的组合,就能写出精确到字符串内容的类型约束。