Kodokon kodokon.com

模板字面量类型:在编译期计算出的字符串

利用模板字面量类型通过笛卡尔积生成联合类型,并用 infer 解析字符串字面量。

9 分钟 · 3 题

在 Kodokon 中打开本课

模板字面量类型把模板字面量语法带入了类型的世界。插入一个联合类型,编译器就会计算笛卡尔积:每一种可能的组合都会成为结果的一个成员。这是让你能精确到具体字符地为事件键、路由或设计系统的 token 建立类型的工具。

TYPESCRIPT
type Lang = "fr" | "en";
type Theme = "light" | "dark";

type ThemeKey = `${Lang}-${Theme}`;
// "fr-light" | "fr-dark" | "en-light" | "en-dark"
两个被插入的联合类型:一个包含 4 个成员的笛卡尔积。

真正的威力在与 infer 结合时显现:模板字面量类型变成了针对字符串的匹配模式。编译器会切分输入的字面量以匹配每个片段;当存在多种切分方式时,分隔符的第一次出现获胜。加上递归,你就得到了在编译期求值的真正的解析器。

TYPESCRIPT
type Split<S extends string, Sep extends string> =
  S extends `${infer Head}${Sep}${infer Rest}`
    ? [Head, ...Split<Rest, Sep>]
    : [S];

type Parts = Split<"a.b.c", ".">;
// ["a", "b", "c"]
一次完全静态的递归切分。

这种模式在提取 Express 风格路由的参数时大放异彩。每次递归调用消费一个 :param 片段,最终的联合类型列出每个参数名,你随后可以把它喂给 Record 来为处理器的签名建立类型。

TYPESCRIPT
type Params<Path extends string> =
  Path extends `${string}:${infer P}/${infer Rest}`
    ? P | Params<Rest>
    : Path extends `${string}:${infer P}`
      ? P
      : never;

type RouteParams = Params<"/users/:id/posts/:postId">;
// "id" | "postId"
在编译期提取路由参数。

TypeScript 提供了四个内建(intrinsic)工具类型 - UppercaseLowercaseCapitalizeUncapitalize - 它们直接在编译器内部实现(通过 intrinsic 关键字),所以你无法自己重写它们。另一个生产模式:string & {}。在联合类型中,string 会吸收掉那些字面量,编辑器也会失去自动补全;与 {} 求交叉会创建一个等价但不同的类型,编译器不会将其化简。

TYPESCRIPT
type Method = "get" | "post";
type Handler = `on${Capitalize<Method>}`;
// "onGet" | "onPost"

type KnownColor = "red" | "green" | "blue";
type Color = KnownColor | (string & {});

declare function paint(color: Color): void;

paint("red");     // suggested by autocompletion
paint("#ff8800"); // also accepted
内建类型以及用于自动补全的 string & {} 技巧。

知识检测

确认你已牢记本课的重点内容。

  1. 如果 A 有 3 个成员,B 有 4 个,那么 ${A}-${B} 包含多少个成员?
    • 7
    • 12
    • 1
    • 4
  2. 当应用于字面量 "hello world" 时,Capitalize 产生什么?
    • "Hello World"
    • "Hello world"
    • "HELLO WORLD"
  3. 为什么要写 KnownColor | (string & {}) 而不是 KnownColor | string
    • 为了禁止 KnownColor 之外的字符串
    • 为了阻止 string 吸收字面量并保留自动补全
    • 为了改善编译器性能
    • 这两种形式严格等价