阿里云主机折上折
  • 微信号
Current Site:Index > Template literal types translate this sentence into English.

Template literal types translate this sentence into English.

Author:Chuan Chen 阅读数:60224人阅读 分类: TypeScript

Basic Concepts of Template Literal Types

TypeScript 4.1 introduced template literal types, a significant enhancement to the type system. It allows developers to manipulate types similarly to how template strings are used in JavaScript, but at the type level rather than the value level. This type can construct new string literal types based on existing string literal types.

type World = "world";
type Greeting = `hello ${World}`;  // "hello world"

Template literal types are enclosed in backticks (``) and use the ${T} syntax to interpolate other types. When the interpolated type is a string literal, numeric literal, boolean literal, or large enum member, the template literal type converts these types into their string representations.

Syntax Features of Template Literal Types

Template literal types support syntax similar to JavaScript template strings:

type EventName<T extends string> = `${T}Changed`;
type Concat<S1 extends string, S2 extends string> = `${S1}${S2}`;

Key features include:

  1. Can contain string literals, union types, or other template literal types
  2. Supports recursive definitions
  3. Can be combined with conditional types and mapped types
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
type Alignment = `${VerticalAlignment}-${HorizontalAlignment}`;
// Results in "top-left" | "top-center" | "top-right" | 
// "middle-left" | "middle-center" | "middle-right" | 
// "bottom-left" | "bottom-center" | "bottom-right"

Interaction with Union Types

When a template literal type contains a union type, TypeScript computes all possible combinations:

type Size = "small" | "medium" | "large";
type Color = "red" | "blue" | "green";
type Style = `${Size}-${Color}`;
/* 
Results in:
"small-red" | "small-blue" | "small-green" |
"medium-red" | "medium-blue" | "medium-green" |
"large-red" | "large-blue" | "large-green"
*/

This feature is particularly useful for creating type-safe CSS class names, event names, and similar scenarios.

Utility Type Tools

TypeScript provides built-in utility types to work with template literal types:

type Uppercase<S extends string> = intrinsic;
type Lowercase<S extends string> = intrinsic;
type Capitalize<S extends string> = intrinsic;
type Uncapitalize<S extends string> = intrinsic;

These utility types can be used directly within template literal types:

type GetterName<T extends string> = `get${Capitalize<T>}`;
type SetterName<T extends string> = `set${Capitalize<T>}`;

Combining with Mapped Types

Template literal types can be combined with mapped types to create powerful type transformations:

type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};

interface Person {
    name: string;
    age: number;
}

type PersonGetters = Getters<Person>;
/*
Equivalent to:
{
    getName: () => string;
    getAge: () => number;
}
*/

Type Inference with Template Literals

TypeScript can extract parts of template literal types:

type ExtractVerb<S extends string> = 
    S extends `${infer Verb} ${string}` ? Verb : never;

type Action = ExtractVerb<"fetch user">;  // "fetch"

This pattern-matching capability makes template literal types extremely powerful for parsing string formats.

Practical Use Cases

1. Type-Safe CSS Class Names

type Color = "red" | "blue" | "green";
type Size = "sm" | "md" | "lg";
type ButtonClass = `btn-${Color}-${Size}`;

function getButtonClass(color: Color, size: Size): ButtonClass {
    return `btn-${color}-${size}`;
}

2. Event Handling

type EventType = "click" | "hover" | "drag";
type ElementID = "header" | "footer" | "sidebar";
type EventName = `${ElementID}_${EventType}`;

function handleEvent(event: EventName) {
    // Handle event
}

handleEvent("header_click");  // Correct
handleEvent("main_hover");    // Error

3. API Routes

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiRoute = `/api/${string}`;
type FullRoute = `${HttpMethod} ${ApiRoute}`;

function fetchRoute(route: FullRoute) {
    // Make request
}

fetchRoute("GET /api/users");  // Correct
fetchRoute("POST /users");     // Error

Advanced Patterns

Recursive Template Types

type Join<T extends string[], D extends string> =
    T extends [] ? '' :
    T extends [infer F] ? F :
    T extends [infer F, ...infer R] ?
    `${F & string}${D}${Join<R & string[], D>}` :
    string;

type Path = Join<["user", "profile", "settings"], "/">;  // "user/profile/settings"

String Parsing

type ParseQueryString<S extends string> =
    S extends `${infer Param}&${infer Rest}`
        ? { [K in Param | keyof ParseQueryString<Rest>]: string }
        : S extends `${infer Param}`
        ? { [K in Param]: string }
        : never;

type QueryParams = ParseQueryString<"name=john&age=30">;
/*
Equivalent to:
{
    name: string;
    age: string;
}
*/

Performance Considerations

While template literal types are powerful, overuse can lead to degraded type-checking performance, especially when dealing with large union types or deep recursion. In complex scenarios, consider:

  1. Limiting the number of union types
  2. Avoiding excessively deep recursion
  3. Using type assertions where appropriate
// Example that may impact performance
type LongUnion = 
    `${"a" | "b" | "c" | "d" | "e"}${"1" | "2" | "3" | "4" | "5"}`;
// Produces 5x5=25 combinations

Combining with Conditional Types

Template literal types combined with conditional types enable more complex type operations:

type ExtractRouteParams<T extends string> =
    T extends `${string}:${infer Param}/${infer Rest}`
        ? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
        : T extends `${string}:${infer Param}`
        ? { [K in Param]: string }
        : {};

type Params = ExtractRouteParams<"/user/:id/profile/:section">;
/*
Equivalent to:
{
    id: string;
    section: string;
}
*/

Type Guards with Template Literals

Custom type guards can be created to check if a string matches a specific template:

function isApiRoute(path: string): path is `/api/${string}` {
    return path.startsWith("/api/");
}

const route = "/api/users";
if (isApiRoute(route)) {
    // Here, the type of route is narrowed to `/api/${string}`
}

Limitations of Template Literal Types

Despite their power, template literal types have some limitations:

  1. Cannot dynamically generate infinite type combinations
  2. Complex templates may slow down type checking
  3. Some pattern-matching scenarios may lack flexibility
  4. May exhibit unexpected behavior when combined with certain advanced type features
// Cannot represent arbitrary-length repeating patterns
type Repeated<T extends string, N extends number> = ... // Not implementable

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.