Capítulo 11 de 36

Chapter 11: Template Literal Types

Core Idea

Template literal types use JS template-literal syntax at the type level — concatenating string literal types, expanding unions into every possible combination, and (via generic inference) deriving one part of a string type from another.

Key Concepts

  • Basic interpolation: `hello ${World}` with World a string literal type produces a new, concatenated string literal type.
  • Union expansion: interpolating a union produces the union of every possible resulting string — each member gets substituted independently. Multiple interpolated positions with unions get cross-multiplied (every combination of every union), so a template with two union placeholders produces the full Cartesian product as a type. Recommended only for smaller cases — prefer ahead-of-time generation for large string unions to avoid type-checking blowup.
  • Deriving string types from an object's own keys: `${string & keyof Type}Changed` constrains a parameter to exactly "one of Type's keys, with Changed appended" — catches typos and using the bare key instead of the derived event name as compile errors, instead of accepting any string.
  • Inference through template literals: a generic method like on<Key extends string & keyof Type>(eventName: \${Key}Changed`, callback: (v: Type[Key]) => void) lets TypeScript work backward from a call-site string ("firstNameChanged") to infer Key ("firstName"), then use indexed access (Type[Key]) to type the callback argument correctly per-property — turning a naive any`-typed callback into one whose parameter type tracks the specific property that changed.
  • Intrinsic string manipulation types — built into the compiler (not found in any .d.ts, for performance), operate on string literal types using the same logic as the equivalent JS runtime string methods (not locale-aware):
    • Uppercase<S> / Lowercase<S>: transform every character.
    • Capitalize<S> / Uncapitalize<S>: transform only the first character.

Code Examples

type PropEventSource<Type> = {
  on<Key extends string & keyof Type>(
    eventName: `${Key}Changed`,
    callback: (newValue: Type[Key]) => void
  ): void;
};
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;

const person = makeWatchedObject({ firstName: "Saoirse", age: 26 });
person.on("firstNameChanged", (v) => v.toUpperCase()); // v: string
person.on("ageChanged", (v) => v < 0);                 // v: number
  • What it demonstrates: template literal type inference deriving the callback's parameter type from the event name string itself, via a generic Key bound to the source object's keys.

Reference Tables

Intrinsic typeEffect
Uppercase<S>every character → uppercase
Lowercase<S>every character → lowercase
Capitalize<S>first character → uppercase
Uncapitalize<S>first character → lowercase

Key Takeaways

  1. Use `${string & keyof T}Suffix` to constrain a string parameter to a derived form of an object's actual keys — it turns "typo in an event name" into a compile error instead of a silent runtime no-op.
  2. Combine a template literal parameter with a generic Key and indexed access (Type[Key]) whenever a callback's argument type should track which variant/property triggered it.
  3. Watch for combinatorial blowup: two or more union-typed interpolation positions multiply together — fine for small unions, but generate large string sets ahead of time rather than relying on template literal types for anything sizeable.

Connects To

  • Everyday Types: string literal types, the base ingredient template literal types build on.
  • Mapped Types: as key-remapping clauses commonly use template literal types to derive new property names (e.g. get${Capitalize<...>}).
  • Conditional Types / infer: template literal patterns can also be matched and destructured via infer inside a conditional type.