template-literal-types
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseUse Template Literal Types to Model DSLs and String Relationships
使用Template Literal Types建模DSL与字符串关系
Overview
概述
Template literal types bring the power of JavaScript template literals to TypeScript's type system. They allow you to model structured subsets of strings, parse domain-specific languages (DSLs), and capture relationships between string types. Combined with conditional types and the keyword, they enable sophisticated string manipulation at the type level.
inferThis skill is essential for bringing type safety to string-heavy APIs and for building powerful type transformations.
Template Literal Types将JavaScript模板字面量的能力引入TypeScript的类型系统。它们允许你对字符串的结构化子集进行建模、解析领域特定语言(DSL),并捕获字符串类型之间的关系。结合条件类型与关键字,它们能在类型层面实现复杂的字符串操作。
infer这项技能对于为字符串密集型API带来类型安全性,以及构建强大的类型转换至关重要。
When to Use This Skill
何时使用该技能
- Modeling structured string patterns (IDs, paths, URLs)
- Parsing domain-specific languages (CSS selectors, query languages)
- Transforming string types (camelCase, snake_case conversion)
- Validating string formats at compile time
- Combining with mapped types for key transformations
- 结构化字符串模式建模(ID、路径、URL等)
- 解析领域特定语言(CSS选择器、查询语言等)
- 字符串类型转换(驼峰式、蛇形命名转换)
- 编译时验证字符串格式
- 与映射类型结合实现键转换
The Iron Rule
核心准则
Use template literal types to model structured string subsets and DSLs. Combine with for parsing and mapped types for transformations.
infer使用Template Literal Types对结构化字符串子集和DSL进行建模。结合进行解析,搭配映射类型实现转换。
inferDetection
适用场景识别
Watch for these opportunities:
typescript
// RED FLAGS - Untyped strings that could be precise
type EventType = string; // Could be 'click' | 'hover' | etc.
function query(selector: string): Element; // Could parse CSS selectors
type CSSProperty = string; // Could validate property names留意以下可应用该技能的场景:
typescript
// 警示信号 - 可被精确化的无类型字符串
type EventType = string; // 可定义为 'click' | 'hover' | 等具体类型
function query(selector: string): Element; // 可解析CSS选择器
type CSSProperty = string; // 可验证属性名称Basic Template Literal Types
基础Template Literal Types
typescript
// Match strings starting with a prefix
type PseudoString = `pseudo${string}`;
const science: PseudoString = 'pseudoscience'; // OK
const alias: PseudoString = 'pseudonym'; // OK
const physics: PseudoString = 'physics'; // Error!
// Match specific patterns
type DataAttribute = `data-${string}`;
type HTTPSUrl = `https://${string}`;
type VersionString = `v${number}.${number}.${number}`;typescript
// 匹配以特定前缀开头的字符串
type PseudoString = `pseudo${string}`;
const science: PseudoString = 'pseudoscience'; // 合法
const alias: PseudoString = 'pseudonym'; // 合法
const physics: PseudoString = 'physics'; // 错误!
// 匹配特定模式
type DataAttribute = `data-${string}`;
type HTTPSUrl = `https://${string}`;
type VersionString = `v${number}.${number}.${number}`;Index Signatures with Template Literals
结合模板字面量的索引签名
typescript
// Allow data-* attributes while keeping type safety
interface Checkbox {
id: string;
checked: boolean;
[key: `data-${string}`]: unknown;
}
const check: Checkbox = {
id: 'subscribe',
checked: true,
'data-listIds': 'all-the-lists', // OK
value: 'yes', // Error: not data-* and not known property
};typescript
// 在保持类型安全的同时允许data-*属性
interface Checkbox {
id: string;
checked: boolean;
[key: `data-${string}`]: unknown;
}
const check: Checkbox = {
id: 'subscribe',
checked: true,
'data-listIds': 'all-the-lists', // 合法
value: 'yes', // 错误:既不是data-*属性也不是已知属性
};Parsing with infer
infer使用infer
进行解析
inferExtract parts of strings using conditional types with :
infertypescript
// Extract event name from handler type
type EventName<T> = T extends `on${infer Name}` ? Name : never;
type ClickEvent = EventName<'onClick'>; // 'Click'
type HoverEvent = EventName<'onMouseEnter'>; // 'MouseEnter'
type BadEvent = EventName<'handleClick'>; // never
// Extract path parameters
type PathParams<T> = T extends `/users/${infer UserId}/posts/${infer PostId}`
? { userId: UserId; postId: PostId }
: never;
type Params = PathParams<'/users/123/posts/456'>;
// { userId: '123'; postId: '456' }结合带的条件类型提取字符串的部分内容:
infertypescript
// 从处理器类型中提取事件名称
type EventName<T> = T extends `on${infer Name}` ? Name : never;
type ClickEvent = EventName<'onClick'>; // 'Click'
type HoverEvent = EventName<'onMouseEnter'>; // 'MouseEnter'
type BadEvent = EventName<'handleClick'>; // never
// 提取路径参数
type PathParams<T> = T extends `/users/${infer UserId}/posts/${infer PostId}`
? { userId: UserId; postId: PostId }
: never;
type Params = PathParams<'/users/123/posts/456'>;
// { userId: '123'; postId: '456' }String Transformations
字符串转换
Build recursive types to transform strings:
typescript
// Convert snake_case to camelCase
type CamelCase<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<CamelCase<Tail>>}`
: S;
type T1 = CamelCase<'foo'>; // 'foo'
type T2 = CamelCase<'foo_bar'>; // 'fooBar'
type T3 = CamelCase<'foo_bar_baz'>; // 'fooBarBaz'
// Apply to object keys
type CamelCaseKeys<T> = {
[K in keyof T as CamelCase<K & string>]: T[K]
};
type SnakeCase = { user_name: string; email_address: string };
type Camel = CamelCaseKeys<SnakeCase>;
// { userName: string; emailAddress: string }构建递归类型以转换字符串:
typescript
// 将蛇形命名转换为驼峰命名
type CamelCase<S extends string> =
S extends `${infer Head}_${infer Tail}`
? `${Head}${Capitalize<CamelCase<Tail>>}`
: S;
type T1 = CamelCase<'foo'>; // 'foo'
type T2 = CamelCase<'foo_bar'>; // 'fooBar'
type T3 = CamelCase<'foo_bar_baz'>; // 'fooBarBaz'
// 应用于对象键
type CamelCaseKeys<T> = {
[K in keyof T as CamelCase<K & string>]: T[K]
};
type SnakeCase = { user_name: string; email_address: string };
type Camel = CamelCaseKeys<SnakeCase>;
// { userName: string; emailAddress: string }Real-World Example: CSS Selectors
实际案例:CSS选择器
typescript
// Enhance querySelector with precise types
type HTMLTag = keyof HTMLElementTagNameMap;
declare global {
interface ParentNode {
// Simple tag selector
querySelector<TagName extends HTMLTag>(
selector: TagName
): HTMLElementTagNameMap[TagName] | null;
// Tag#id selector
querySelector<TagName extends HTMLTag>(
selector: `${TagName}#${string}`
): HTMLElementTagNameMap[TagName] | null;
}
}
// Usage
const img = document.querySelector('img#hero');
// Type: HTMLImageElement | null
// Can access img?.src, img?.alt, etc.
const div = document.querySelector('div#container');
// Type: HTMLDivElement | nulltypescript
// 为querySelector增强精确类型
type HTMLTag = keyof HTMLElementTagNameMap;
declare global {
interface ParentNode {
// 简单标签选择器
querySelector<TagName extends HTMLTag>(
selector: TagName
): HTMLElementTagNameMap[TagName] | null;
// 标签#id选择器
querySelector<TagName extends HTMLTag>(
selector: `${TagName}#${string}`
): HTMLElementTagNameMap[TagName] | null;
}
}
// 使用示例
const img = document.querySelector('img#hero');
// 类型:HTMLImageElement | null
// 可访问img?.src, img?.alt等属性
const div = document.querySelector('div#container');
// 类型:HTMLDivElement | nullCombining with Mapped Types
与映射类型结合使用
typescript
// Create event handler types from event names
type EventMap = {
click: MouseEvent;
keydown: KeyboardEvent;
submit: SubmitEvent;
};
type EventHandlers<Events extends Record<string, Event>> = {
[K in keyof Events as `on${Capitalize<K & string>}`]?:
(event: Events[K]) => void;
};
type Handlers = EventHandlers<EventMap>;
// {
// onClick?: (event: MouseEvent) => void;
// onKeydown?: (event: KeyboardEvent) => void;
// onSubmit?: (event: SubmitEvent) => void;
// }typescript
// 从事件名称创建事件处理器类型
type EventMap = {
click: MouseEvent;
keydown: KeyboardEvent;
submit: SubmitEvent;
};
type EventHandlers<Events extends Record<string, Event>> = {
[K in keyof Events as `on${Capitalize<K & string>}`]?:
(event: Events[K]) => void;
};
type Handlers = EventHandlers<EventMap>;
// {
// onClick?: (event: MouseEvent) => void;
// onKeydown?: (event: KeyboardEvent) => void;
// onSubmit?: (event: SubmitEvent) => void;
// }Pressure Resistance Protocol
应对质疑的方法
When pressured to use simple types:
string- Identify patterns: What structure do the strings have?
- Start simple: Use unions of literal types first
- Add templates: Use template literals for infinite but structured sets
- Consider parsing: Use to extract information
infer - Test edge cases: Ensure your types are accurate, not just precise
当被要求使用简单类型时:
string- 识别模式:这些字符串有什么结构?
- 从简入手:先使用字面量类型的联合
- 添加模板:使用模板字面量处理无限但结构化的集合
- 考虑解析:使用提取信息
infer - 测试边缘案例:确保你的类型准确而非过度精确
Red Flags
警示信号
| Anti-Pattern | Why It's Bad |
|---|---|
| Misses validation opportunity |
| Complex template types without testing | May be inaccurate |
| Parsing without escape hatches | Complex selectors need fallback |
| Overly precise types | Can break legitimate use cases |
| 反模式 | 危害 |
|---|---|
| 错失验证机会 |
| 未测试的复杂模板类型 | 可能不准确 |
| 无退路的解析逻辑 | 复杂选择器需要 fallback |
| 过度精确的类型 | 可能破坏合法使用场景 |
Common Rationalizations
常见辩解及回应
"String is good enough"
“用string就够了”
Reality: Template literals catch typos and invalid formats at compile time. vs can be caught immediately.
'user-123''users-123'实际情况:模板字面量能在编译时捕获拼写错误和无效格式。比如与的错误能被立即发现。
'user-123''users-123'"This is too complex"
“这太复杂了”
Reality: Start simple with prefix patterns, then add complexity as needed. Even basic template literals provide value.
实际情况:从简单的前缀模式开始,再根据需要增加复杂度。即使是基础的模板字面量也能带来价值。
"It will hurt performance"
“会影响性能”
Reality: Template literal types are evaluated at compile time. They have no runtime cost.
实际情况:Template Literal Types在编译时求值,不会带来运行时开销。
Quick Reference
快速参考
| Pattern | Syntax | Use Case |
|---|---|---|
| Prefix | | data attributes |
| Suffix | | event names |
| Middle | | file extensions |
| Extract | | parsing |
| Transform | | camelCase |
| 模式 | 语法 | 适用场景 |
|---|---|---|
| 前缀 | | 数据属性 |
| 后缀 | | 事件名称 |
| 中间 | | 文件扩展名 |
| 提取 | | 解析操作 |
| 转换 | | 驼峰命名转换 |
The Bottom Line
总结
Template literal types bring type safety to string-heavy code. Use them to model structured strings, parse DSLs, and transform types. Combined with and mapped types, they enable powerful type-level string manipulation.
inferTemplate Literal Types为字符串密集型代码带来类型安全性。使用它们建模结构化字符串、解析DSL并转换类型。结合与映射类型,能实现强大的类型层面字符串操作。
inferReference
参考资料
- Effective TypeScript, 2nd Edition by Dan Vanderkam
- Item 54: Use Template Literal Types to Model DSLs and Relationships Between Strings
- 《Effective TypeScript(第二版)》作者Dan Vanderkam
- 条目54:使用Template Literal Types建模DSL与字符串关系