模板字面量类型
概述
模板字面量类型(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"模板字面量类型让我们能够:
- 动态组合字符串类型:在类型层面组合和操作字符串
- 精确的类型定义:创建更精确的字符串字面量类型
- 类型级字符串操作:实现字符串的拼接、转换等操作
- 模式匹配:通过字符串模式进行类型匹配和转换
基本语法
基本模板字面量类型
模板字面量类型使用反引号和 ${} 语法:
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 类名生成
- 事件名称类型
- 数据库字段名转换
- 环境变量名称生成
- 国际化键类型
- 字符串模式匹配
- 框架和库的类型定义
相关链接
- 字面量类型 - 了解字符串字面量类型的基础
- 映射类型 - 学习映射类型与模板字面量类型的结合使用
- 条件类型 - 了解条件类型在字符串模式匹配中的应用
- 联合类型 - 了解联合类型与模板字面量类型的组合
- TypeScript 官方文档 - 模板字面量类型