template-literal-types

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Use 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
infer
keyword, they enable sophisticated string manipulation at the type level.
This 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
infer
for parsing and mapped types for transformations.
使用Template Literal Types对结构化字符串子集和DSL进行建模。结合
infer
进行解析,搭配映射类型实现转换。

Detection

适用场景识别

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
进行解析

Extract parts of strings using conditional types with
infer
:
typescript
// 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' }
结合带
infer
的条件类型提取字符串的部分内容:
typescript
// 从处理器类型中提取事件名称
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 | null
typescript
// 为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 | null

Combining 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
string
types:
  1. Identify patterns: What structure do the strings have?
  2. Start simple: Use unions of literal types first
  3. Add templates: Use template literals for infinite but structured sets
  4. Consider parsing: Use
    infer
    to extract information
  5. Test edge cases: Ensure your types are accurate, not just precise
当被要求使用简单
string
类型时:
  1. 识别模式:这些字符串有什么结构?
  2. 从简入手:先使用字面量类型的联合
  3. 添加模板:使用模板字面量处理无限但结构化的集合
  4. 考虑解析:使用
    infer
    提取信息
  5. 测试边缘案例:确保你的类型准确而非过度精确

Red Flags

警示信号

Anti-PatternWhy It's Bad
type ID = string
Misses validation opportunity
Complex template types without testingMay be inaccurate
Parsing without escape hatchesComplex selectors need fallback
Overly precise typesCan break legitimate use cases
反模式危害
type ID = string
错失验证机会
未测试的复杂模板类型可能不准确
无退路的解析逻辑复杂选择器需要 fallback
过度精确的类型可能破坏合法使用场景

Common Rationalizations

常见辩解及回应

"String is good enough"

“用string就够了”

Reality: Template literals catch typos and invalid formats at compile time.
'user-123'
vs
'users-123'
can be caught immediately.
实际情况:模板字面量能在编译时捕获拼写错误和无效格式。比如
'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

快速参考

PatternSyntaxUse Case
Prefix
`data-${string}`
data attributes
Suffix
`${string}Event`
event names
Middle
`${string}.${string}`
file extensions
Extract
`on${infer Name}`
parsing
Transform
`${Head}${Capitalize<Tail>}`
camelCase
模式语法适用场景
前缀
`data-${string}`
数据属性
后缀
`${string}Event`
事件名称
中间
`${string}.${string}`
文件扩展名
提取
`on${infer Name}`
解析操作
转换
`${Head}${Capitalize<Tail>}`
驼峰命名转换

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
infer
and mapped types, they enable powerful type-level string manipulation.
Template Literal Types为字符串密集型代码带来类型安全性。使用它们建模结构化字符串、解析DSL并转换类型。结合
infer
与映射类型,能实现强大的类型层面字符串操作。

Reference

参考资料

  • 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与字符串关系