常用类型

发布于 2026-08-31

模板字面量类型

介绍 TypeScript 模板字面量类型:在类型层面对字符串进行组合、约束与推导,区分于 JavaScript 运行时模版字符串,并涵盖联合类型组合、字符串模式、keyof 与 infer 等用法。

概念

Template Literal Types,模板字面量类型。

  1. 是 TypeScript 在类型层面对字符串进行组合、约束和推导;
  2. 属于 TypeScript 特有的类型系统,原生 JavaScript 中并没有这个概念;
  3. 需要区分 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