阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 模板字面量类型

模板字面量类型

作者:陈川 阅读数:60223人阅读 分类: TypeScript

模板字面量类型的基本概念

TypeScript 4.1 引入了模板字面量类型,这是对类型系统的重要增强。它允许开发者像使用 JavaScript 中的模板字符串一样操作类型,但作用在类型层面而非值层面。这种类型可以基于字符串字面量类型构建新的字符串字面量类型。

type World = "world";
type Greeting = `hello ${World}`;  // "hello world"

模板字面量类型使用反引号(``)包裹,并通过${T}语法插入其他类型。当插入的类型是字符串字面量、数字字面量、布尔字面量或大枚举成员时,模板字面量类型会将这些类型转换为它们的字符串表示形式。

模板字面量类型的语法特性

模板字面量类型支持与 JavaScript 模板字符串相似的语法:

type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;

它有几个关键特性:

  1. 可以包含字符串字面量、联合类型或其他模板字面量类型
  2. 支持递归定义
  3. 可以与条件类型和映射类型结合使用
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
type Alignment = `${VerticalAlignment}-${HorizontalAlignment}`;
// 结果为 "top-left" | "top-center" | "top-right" | 
// "middle-left" | "middle-center" | "middle-right" | 
// "bottom-left" | "bottom-center" | "bottom-right"

与联合类型的交互

当模板字面量类型中包含联合类型时,TypeScript 会计算所有可能的组合:

type Size = "small" | "medium" | "large";
type Color = "red" | "blue" | "green";
type Style = `${Size}-${Color}`;
/* 
结果为:
"small-red" | "small-blue" | "small-green" |
"medium-red" | "medium-blue" | "medium-green" |
"large-red" | "large-blue" | "large-green"
*/

这种特性在创建类型安全的 CSS 类名、事件名称等场景特别有用。

实用类型工具

TypeScript 提供了一些内置的工具类型来配合模板字面量类型:

type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;

这些工具类型可以直接在模板字面量类型中使用:

type GetterName<T extends string> = `get${Capitalize<T>}`;
type SetterName<T extends string> = `set${Capitalize<T>}`;

与映射类型结合

模板字面量类型与映射类型结合可以创建强大的类型转换:

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

interface Person {
    name: string;
    age: number;
}

type PersonGetters = Getters<Person>;
/*
等同于:
{
    getName: () => string;
    getAge: () => number;
}
*/

类型推断与模板字面量

TypeScript 可以从模板字面量类型中提取部分内容:

type ExtractVerb<S extends string> = 
    S extends `${infer Verb} ${string}` ? Verb : never;

type Action = ExtractVerb<"fetch user">;  // "fetch"

这种模式匹配能力使得模板字面量类型在解析字符串格式时非常强大。

实际应用场景

1. 类型安全的 CSS 类名

type Color = "red" | "blue" | "green";
type Size = "sm" | "md" | "lg";
type ButtonClass = `btn-${Color}-${Size}`;

function getButtonClass(color: Color, size: Size): ButtonClass {
    return `btn-${color}-${size}`;
}

2. 事件处理

type EventType = "click" | "hover" | "drag";
type ElementID = "header" | "footer" | "sidebar";
type EventName = `${ElementID}_${EventType}`;

function handleEvent(event: EventName) {
    // 处理事件
}

handleEvent("header_click");  // 正确
handleEvent("main_hover");    // 错误

3. API 路由

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiRoute = `/api/${string}`;
type FullRoute = `${HttpMethod} ${ApiRoute}`;

function fetchRoute(route: FullRoute) {
    // 发起请求
}

fetchRoute("GET /api/users");  // 正确
fetchRoute("POST /users");     // 错误

高级模式

递归模板类型

type Join<T extends string[], D extends string> =
    T extends [] ? '' :
    T extends [infer F] ? F :
    T extends [infer F, ...infer R] ?
    `${F & string}${D}${Join<R & string[], D>}` :
    string;

type Path = Join<["user", "profile", "settings"], "/">;  // "user/profile/settings"

字符串解析

type ParseQueryString<S extends string> =
    S extends `${infer Param}&${infer Rest}`
        ? { [K in Param | keyof ParseQueryString<Rest>]: string }
        : S extends `${infer Param}`
        ? { [K in Param]: string }
        : never;

type QueryParams = ParseQueryString<"name=john&age=30">;
/*
等同于:
{
    name: string;
    age: string;
}
*/

性能考虑

虽然模板字面量类型功能强大,但过度使用可能导致类型检查性能下降,特别是在处理大型联合类型或深度递归时。在复杂场景中,可能需要考虑:

  1. 限制联合类型的数量
  2. 避免过深的递归
  3. 在适当的地方使用类型断言
// 可能影响性能的例子
type LongUnion = 
    `${"a" | "b" | "c" | "d" | "e"}${"1" | "2" | "3" | "4" | "5"}`;
// 会产生 5x5=25 种组合

与条件类型的结合

模板字面量类型与条件类型结合可以实现更复杂的类型操作:

type ExtractRouteParams<T extends string> =
    T extends `${string}:${infer Param}/${infer Rest}`
        ? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
        : T extends `${string}:${infer Param}`
        ? { [K in Param]: string }
        : {};

type Params = ExtractRouteParams<"/user/:id/profile/:section">;
/*
等同于:
{
    id: string;
    section: string;
}
*/

类型守卫与模板字面量

可以创建自定义类型守卫来检查字符串是否符合特定模板:

function isApiRoute(path: string): path is `/api/${string}` {
    return path.startsWith("/api/");
}

const route = "/api/users";
if (isApiRoute(route)) {
    // 这里 route 的类型被收窄为 `/api/${string}`
}

模板字面量类型的限制

尽管功能强大,模板字面量类型也有一些限制:

  1. 不能动态生成无限的类型组合
  2. 复杂的模板可能导致类型检查变慢
  3. 某些模式匹配场景可能不够灵活
  4. 与某些其他高级类型特性结合时可能有意外行为
// 无法表示任意长度的重复模式
type Repeated<T extends string, N extends number> = ... // 无法实现

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌