Template literal types translate this sentence into English.
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:
- Can contain string literals, union types, or other template literal types
- Supports recursive definitions
- 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:
- Limiting the number of union types
- Avoiding excessively deep recursion
- 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:
- Cannot dynamically generate infinite type combinations
- Complex templates may slow down type checking
- Some pattern-matching scenarios may lack flexibility
- 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
上一篇:Vue.js核心知识点
下一篇:never类型与void类型