概念
Template Literal Types,模板字面量类型。
- 是
TypeScript在类型层面对字符串进行组合、约束和推导; - 属于
TypeScript特有的类型系统,原生JavaScript中并没有这个概念; - 需要区分
JavaScript中的 Template Literal 模版字符串;JavaScript的是值,发生在运行时;TypeScript的是类型,发生在类型检查阶段;
基础知识补充
- ` 符号叫 反引号 backtick;
${}:interpolation 插值,${expression}表达式插值,$表示要插入一个值,{}包住要执行的表达式;keyof:TypeScript 关键字/类型元素符 type operator,用来获取一个类型所有键;infer:TypeScript 关键字,用于条件类型中进行类型推断 type inference;
理解
1. 区别 JavaScript 模版字符串
JavaScript 有 Template Literal 模版字面量,是运行时的值。比如:
const name = "OceanZ";
const message = `Hello, ${name}!`;
console.log(message);
// message 的值是 "Hello, OceanZ!"2. TypeScript 中的 Literal Type 字面量类型
Template Literal Type 的基础是 Literal Type 字面量类型,比如:
// 可以是任意字符串
let direction: string如下写法:
type Direction = "left" | "right" | "up" | "down";
// 只能是 `"left" | "right" | "up" | "down"` 中的一个:
let direction: Direction其中
|是Union Type 联合类型操作符,表示一个值可以是多个类型中的一个。
left 在 TypeScript 类型位置表示的是:字符串字面量类型 "left"。
上面的类型定义,本质表达的是:
Direction 可以是以下中的一个:
├── "left"
├── "right"
├── "up"
└── "down"3. Template Literal Type 模板字面量类型
比如:
type Direction = "left" | "right"
type ClassName = `text-${Direction}`那么,ClassName 实质相当于:
type ClassName = "text-left" | "text-right"这个是 Template Literal Type 最核心的能力,再比如:
type Size = "sm" | "md" | "lg"
type ClassName = `p-${Size}`得到:
"p-sm" | "p-md" | "p-lg"模板字面量类型 = 对 Literal Type 进行字符串拼接。
结论
Template Literal Type 是在上述的基础上增加的类型,不是用来生成字符串,而是描述:什么样的字符串才符合这个类型。比如:
type Greeting = `Hello, ${string}!`;
const a: Greeting = "Hello Ocean"; // OK ✅
const b: Greeting = "Hello Tom"; // OK ✅
const c: Greeting = "Hi Ocean"; // Error ❌要和 JavaScript 进行区分:
// JavaScript:值
const value = `Hello ${name}`;
// TypeScript:类型
type Value = `Hello ${string}`;使用场景
Union 结合
Union Type 联合类型 和 Template Literal Type 结合使用,可以实现更精确的类型约束。
比如:
type Direction = "top" | "bottom";
type Size = "sm" | "lg";
type ClassName = `${Direction}-${Size}`;TypeScript 会计算笛卡尔积:
type ClassName =
| "top-sm"
| "top-lg"
| "bottom-sm"
| "bottom-lg";可以理解成:2 个 Direction × 2 个 Size = 4 种字符串。
type Locale = "zh" | "en";
type Page = "home" | "about";
type Route = `/${Locale}/${Page}`;得到如下:
type Route =
| "/zh/home"
| "/zh/about"
| "/en/home"
| "/en/about";在 Next.js、路由、CSS class、事件名称、配置 key 等地方非常实用。
字符串模式
例如:
type Path = `/api/${string}`;
const a: Path = "/api/users"; // OK ✅
const b: Path = "/api/products"; // OK ✅
const c: Path = "/users"; // Error ❌表达的不是几个固定字符串,而是一种字符串模式:必须以 /api/ 开头,后面可以是 string。
类似:
type Px = `${number}px`;
const width: Px = "100px"; // OK ✅
const height: Px = "20px"; // OK ✅
const bad: Px = "100"; // Error ❌表达的是:必须以数字后面加 px 结尾。
因此:Template Literal Type 是一种更灵活的类型约束方式,不只是枚举字符串,还可以描述字符串的“形状”。
keyof
type User = {
name: string;
age: number;
};
type UserKeys = keyof User;得到:
"name" | "age"那么:
type EventName = `${keyof User}-Changed`得到:
"name-Changed" | "age-Changed"在事件处理、状态管理、配置 key 等地方非常实用。
继续:
type EventName<T> = `${string & keyof T}-Changed`;
type UserEvent = EventName<User>;拆解就是:
keyof User
↓
"name" | "age"
↓ 放进模板
`${...}Changed`
↓
"nameChanged" | "ageChanged"infer
type GetParam<T> = T extend `:${infer Param}` ? Param : never;
// 使用
type A = GetParam<":id">;
// 结果
type A = "id";如果 T 的格式是:":" + 某个字符串,那么:把后面的字符串提取出来,并命名为 Param。
所以:id 匹配 :${infer Param},得到 Param = "id",在路由库、类型安全 API、路径参数解析里比较常见。
知识链
Literal Type
↓
Union Type
↓
Template Literal Type
↓
字符串 Union 自动组合
↓
${string} / ${number}
↓
keyof + Template Literal Type
↓
Generic + Template Literal Type
↓
Conditional Type + infer