Skip to content

模板字面量类型

概述

模板字面量类型(Template Literal Types)是 TypeScript 4.1 引入的强大特性,它允许我们在类型层面使用模板字符串语法来操作字符串类型。模板字面量类型使用反引号(`)和 ${} 语法,类似于 JavaScript 中的模板字符串,但作用于类型层面。通过模板字面量类型,我们可以创建更加精确和灵活的字符串类型,实现类型级别的字符串操作和模式匹配。

为什么需要模板字面量类型

在没有模板字面量类型的情况下,我们无法在类型层面操作字符串:

typescript
// 没有模板字面量类型:无法动态组合字符串类型
type ApiEndpoint = "getUser" | "createUser" | "updateUser";
type HttpMethod = "GET" | "POST" | "PUT";

// 无法自动生成 "GET /getUser" 这样的组合类型
// 需要手动列出所有可能的组合

// 使用模板字面量类型:可以动态组合字符串类型
type ApiRoute<Method extends HttpMethod, Endpoint extends ApiEndpoint> = 
  `${Method} /${Endpoint}`;

type GetUserRoute = ApiRoute<"GET", "getUser">;
// 类型:"GET /getUser"

模板字面量类型让我们能够:

  1. 动态组合字符串类型:在类型层面组合和操作字符串
  2. 精确的类型定义:创建更精确的字符串字面量类型
  3. 类型级字符串操作:实现字符串的拼接、转换等操作
  4. 模式匹配:通过字符串模式进行类型匹配和转换

基本语法

基本模板字面量类型

模板字面量类型使用反引号和 ${} 语法:

typescript
// 基本语法:`${Type}`
// 在类型层面使用模板字符串语法

type Greeting = `Hello, ${string}`;
// 类型:所有以 "Hello, " 开头的字符串

type UserName = "Alice" | "Bob" | "Charlie";
type GreetingMessage = `Hello, ${UserName}`;
// 类型:"Hello, Alice" | "Hello, Bob" | "Hello, Charlie"

模板字面量类型的工作原理

模板字面量类型通过组合字符串字面量类型来生成新的字符串类型:

typescript
// 字符串字面量类型组合
type First = "Hello";
type Second = "World";
type Combined = `${First} ${Second}`;
// 类型:"Hello World"

// 与联合类型组合
type Method = "GET" | "POST";
type Path = "/users" | "/posts";
type Route = `${Method} ${Path}`;
// 类型:"GET /users" | "GET /posts" | "POST /users" | "POST /posts"

字符串插值

模板字面量类型支持字符串插值,可以插入其他类型:

typescript
// 插入字符串字面量类型
type Name = "TypeScript";
type Message = `Welcome to ${Name}`;
// 类型:"Welcome to TypeScript"

// 插入联合类型(会生成所有可能的组合)
type Prefix = "user" | "admin";
type Action = "create" | "delete";
type EventName = `${Prefix}_${Action}`;
// 类型:"user_create" | "user_delete" | "admin_create" | "admin_delete"

// 插入数字字面量类型
type Version = 1 | 2 | 3;
type ApiVersion = `v${Version}`;
// 类型:"v1" | "v2" | "v3"

字符串操作类型

TypeScript 提供了内置的字符串操作类型,用于在类型层面操作字符串:

Uppercase

将字符串类型转换为大写:

typescript
type UppercaseExample = Uppercase<"hello">;
// 类型:"HELLO"

type Method = "get" | "post";
type UpperMethod = Uppercase<Method>;
// 类型:"GET" | "POST"

Lowercase

将字符串类型转换为小写:

typescript
type LowercaseExample = Lowercase<"HELLO">;
// 类型:"hello"

type Method = "GET" | "POST";
type LowerMethod = Lowercase<Method>;
// 类型:"get" | "post"

Capitalize

将字符串类型的首字母转换为大写:

typescript
type CapitalizeExample = Capitalize<"hello">;
// 类型:"Hello"

type Status = "active" | "inactive";
type CapitalStatus = Capitalize<Status>;
// 类型:"Active" | "Inactive"

Uncapitalize

将字符串类型的首字母转换为小写:

typescript
type UncapitalizeExample = Uncapitalize<"Hello">;
// 类型:"hello"

type Status = "Active" | "Inactive";
type UncapitalStatus = Uncapitalize<Status>;
// 类型:"active" | "inactive"

使用示例

示例 1:API 路由类型

typescript
// 定义 HTTP 方法和端点
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = "users" | "posts" | "comments";

// 使用模板字面量类型生成路由类型
type ApiRoute<Method extends HttpMethod, Path extends Endpoint> = 
  `${Method} /api/${Path}`;

// 使用示例
type GetUsersRoute = ApiRoute<"GET", "users">;
// 类型:"GET /api/users"

type CreatePostRoute = ApiRoute<"POST", "posts">;
// 类型:"POST /api/posts"

// 生成所有可能的路由组合
type AllRoutes = ApiRoute<HttpMethod, Endpoint>;
// 类型:"GET /api/users" | "GET /api/posts" | "GET /api/comments" | 
//       "POST /api/users" | "POST /api/posts" | "POST /api/comments" | ...

示例 2:CSS 类名生成

typescript
// 定义组件和状态
type Component = "button" | "input" | "card";
type State = "active" | "disabled" | "hover";

// 生成 BEM 风格的类名
type BemClass<Block extends Component, Element extends string, Modifier extends State> = 
  `${Block}__${Element}--${Modifier}`;

// 使用示例
type ButtonActive = BemClass<"button", "primary", "active">;
// 类型:"button__primary--active"

type InputDisabled = BemClass<"input", "text", "disabled">;
// 类型:"input__text--disabled"

// 生成所有可能的类名组合
type AllButtonClasses = BemClass<"button", "primary" | "secondary", State>;
// 类型:"button__primary--active" | "button__primary--disabled" | 
//       "button__primary--hover" | "button__secondary--active" | ...

示例 3:事件名称类型

typescript
// 定义事件前缀和动作
type EventPrefix = "user" | "product" | "order";
type Action = "create" | "update" | "delete";

// 生成事件名称类型
type EventName<Prefix extends EventPrefix, Act extends Action> = 
  `${Prefix}_${Capitalize<Act>}ed`;

// 使用示例
type UserCreated = EventName<"user", "create">;
// 类型:"user_Created"

type ProductUpdated = EventName<"product", "update">;
// 类型:"product_Updated"

// 生成所有可能的事件名称
type AllEvents = EventName<EventPrefix, Action>;
// 类型:"user_Created" | "user_Updated" | "user_Deleted" | 
//       "product_Created" | "product_Updated" | "product_Deleted" | ...

示例 4:数据库字段名转换

typescript
// 将驼峰命名转换为下划线命名
type CamelToSnake<T extends string> = 
  T extends `${infer First}${infer Rest}`
    ? First extends Uppercase<First>
      ? `_${Lowercase<First>}${CamelToSnake<Rest>}`
      : `${First}${CamelToSnake<Rest>}`
    : T;

// 使用示例
type SnakeCase = CamelToSnake<"userName">;
// 类型:"user_name"

type SnakeCase2 = CamelToSnake<"firstName">;
// 类型:"first_name"

// 转换对象类型的键名
type KeysToSnakeCase<T> = {
  [K in keyof T as CamelToSnake<string & K>]: T[K];
};

interface User {
  firstName: string;
  lastName: string;
  emailAddress: string;
}

type UserSnakeCase = KeysToSnakeCase<User>;
// 类型:{
//   first_name: string;
//   last_name: string;
//   email_address: string;
// }

示例 5:类型安全的路径拼接

typescript
// 定义路径段
type PathSegment = "users" | "posts" | "comments";
type Id = number;

// 生成类型安全的路径
type ApiPath<Segment extends PathSegment> = `/api/${Segment}`;
type ApiPathWithId<Segment extends PathSegment> = `/api/${Segment}/${Id}`;

// 使用示例
type UsersPath = ApiPath<"users">;
// 类型:"/api/users"

type UserDetailPath = ApiPathWithId<"users">;
// 类型:`/api/users/${number}`

// 更复杂的路径组合
type NestedPath<Parent extends PathSegment, Child extends PathSegment> = 
  `/api/${Parent}/${Id}/${Child}`;

type UserPostsPath = NestedPath<"users", "posts">;
// 类型:`/api/users/${number}/posts`

示例 6:环境变量名称生成

typescript
// 定义应用和环境
type AppName = "myapp" | "admin" | "api";
type Environment = "dev" | "staging" | "prod";

// 生成环境变量名称
type EnvVar<App extends AppName, Env extends Environment, Key extends string> = 
  `${Uppercase<App>}_${Uppercase<Env>}_${Uppercase<Key>}`;

// 使用示例
type DbUrl = EnvVar<"myapp", "prod", "db_url">;
// 类型:"MYAPP_PROD_DB_URL"

type ApiKey = EnvVar<"api", "staging", "api_key">;
// 类型:"API_STAGING_API_KEY"

// 生成所有可能的环境变量名称
type AllEnvVars = EnvVar<AppName, Environment, "db_url" | "api_key">;
// 类型:"MYAPP_DEV_DB_URL" | "MYAPP_DEV_API_KEY" | 
//       "MYAPP_STAGING_DB_URL" | "MYAPP_STAGING_API_KEY" | ...

示例 7:类型安全的国际化键

typescript
// 定义命名空间和键
type Namespace = "common" | "auth" | "dashboard";
type Key = "title" | "description" | "button";

// 生成国际化键类型
type I18nKey<NS extends Namespace, K extends Key> = 
  `${NS}.${K}`;

// 使用示例
type CommonTitle = I18nKey<"common", "title">;
// 类型:"common.title"

type AuthButton = I18nKey<"auth", "button">;
// 类型:"auth.button"

// 生成所有可能的键
type AllI18nKeys = I18nKey<Namespace, Key>;
// 类型:"common.title" | "common.description" | "common.button" | 
//       "auth.title" | "auth.description" | "auth.button" | ...

// 类型安全的翻译函数
function translate<NS extends Namespace, K extends Key>(
  key: I18nKey<NS, K>
): string {
  // 实现翻译逻辑
  return "";
}

// 使用:类型检查确保键名正确
translate("common.title");     // ✅ 正确
translate("auth.button");      // ✅ 正确
// translate("invalid.key");   // ❌ 类型错误

示例 8:字符串模式匹配

typescript
// 提取 URL 协议
type ExtractProtocol<T extends string> = 
  T extends `${infer Protocol}://${string}` 
    ? Protocol 
    : never;

// 使用示例
type HttpProtocol = ExtractProtocol<"http://example.com">;
// 类型:"http"

type HttpsProtocol = ExtractProtocol<"https://example.com">;
// 类型:"https"

// 提取文件扩展名
type ExtractExtension<T extends string> = 
  T extends `${string}.${infer Ext}` 
    ? Ext 
    : never;

// 使用示例
type JsExt = ExtractExtension<"app.js">;
// 类型:"js"

type TsExt = ExtractExtension<"index.ts">;
// 类型:"ts"

// 更复杂的模式:提取路径参数
type ExtractPathParams<T extends string> = 
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractPathParams<`/${Rest}`>
    : T extends `${string}:${infer Param}`
    ? Param
    : never;

// 使用示例
type Params1 = ExtractPathParams<"/users/:id">;
// 类型:"id"

type Params2 = ExtractPathParams<"/users/:id/posts/:postId">;
// 类型:"id" | "postId"

与映射类型结合

模板字面量类型经常与映射类型结合使用,实现更强大的类型操作:

typescript
// 为对象的所有键添加前缀
type AddPrefix<T, Prefix extends string> = {
  [K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K];
};

// 使用示例
interface User {
  name: string;
  age: number;
  email: string;
}

type PrefixedUser = AddPrefix<User, "user">;
// 类型:{
//   userName: string;
//   userAge: number;
//   userEmail: string;
// }

// 为对象的所有键添加后缀
type AddSuffix<T, Suffix extends string> = {
  [K in keyof T as `${string & K}${Suffix}`]: T[K];
};

type SuffixedUser = AddSuffix<User, "Value">;
// 类型:{
//   nameValue: string;
//   ageValue: number;
//   emailValue: string;
// }

类型检查示例

常见错误

typescript
// ❌ 错误:模板字面量类型语法错误
// type Wrong = `${string}`;  // 缺少具体类型

// ❌ 错误:不能使用非字符串类型
// type Wrong = `${number}`;  // 数字类型不能直接用于模板字面量

// ❌ 错误:复杂的类型操作可能无法推断
type Complex<T> = T extends string ? `${T}_suffix` : never;
type Test = Complex<string | number>;  // 可能产生意外的联合类型

正确写法

typescript
// ✅ 正确:基本模板字面量类型
type Greeting = `Hello, ${string}`;
type Message = `Hello, ${"Alice" | "Bob"}`;

// ✅ 正确:使用字符串操作类型
type Upper = Uppercase<"hello">;  // "HELLO"
type Lower = Lowercase<"HELLO">;  // "hello"
type Cap = Capitalize<"hello">;   // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"

// ✅ 正确:与映射类型结合
type Prefixed<T> = {
  [K in keyof T as `prefix_${string & K}`]: T[K];
};

// ✅ 正确:字符串模式匹配
type ExtractProtocol<T extends string> = 
  T extends `${infer P}://${string}` ? P : never;

注意事项

提示

  • 模板字面量类型使用反引号和 ${} 语法,类似于 JavaScript 模板字符串
  • 可以使用内置的字符串操作类型(Uppercase、Lowercase、Capitalize、Uncapitalize)来转换字符串
  • 模板字面量类型与联合类型组合时,会生成所有可能的组合
  • 模板字面量类型经常与映射类型、条件类型结合使用,实现复杂的类型操作
  • 模板字面量类型是 TypeScript 4.1+ 的特性,需要确保 TypeScript 版本 >= 4.1

注意

  • 模板字面量类型只能用于字符串类型,不能用于其他类型
  • 复杂的模板字面量类型可能会影响类型检查性能
  • 与联合类型组合时,会产生笛卡尔积,可能导致类型数量爆炸
  • 字符串模式匹配需要精确的模式定义,否则可能无法正确推断
  • 模板字面量类型中的类型推断是延迟的,只有在实际使用时才会计算

重要

  • 模板字面量类型是 TypeScript 类型级编程的重要特性,可以实现强大的字符串类型操作
  • 理解模板字面量类型的工作原理对于掌握高级类型非常重要
  • 模板字面量类型与映射类型、条件类型结合使用,可以构建强大的类型系统
  • 模板字面量类型不会影响运行时的性能,因为类型信息在编译时会被擦除

信息

模板字面量类型的优势

  • 类型级字符串操作:在类型层面操作和组合字符串
  • 精确的类型定义:创建更精确的字符串字面量类型
  • 类型安全:在编译时确保字符串格式的正确性
  • 自文档化:类型定义本身就是文档

模板字面量类型的应用场景

  • API 路由类型定义
  • CSS 类名生成
  • 事件名称类型
  • 数据库字段名转换
  • 环境变量名称生成
  • 国际化键类型
  • 字符串模式匹配
  • 框架和库的类型定义

相关链接

基于 VitePress 构建