Template literal types to funkcja TypeScript 4.1+, która pozwala na tworzenie typów stringowych używając składni template literals z JavaScript. Umożliwia to tworzenie złożonych typów opartych na stringach, manipulację i transformację typów stringowych na poziomie systemu typów.
1// Podstawowe template literal types
2type Greeting = `Hello ${string}`;
3type Welcome = `Welcome to ${string}!`;
4
5// Przykłady użycia
6const greeting1: Greeting = "Hello World"; // OK
7const greeting2: Greeting = "Hello TypeScript"; // OK
8// const greeting3: Greeting = "Hi World"; // Błąd! Musi zaczynać się od "Hello "
9
10// Łączenie literal types
11type EventName = "click" | "focus" | "blur";
12type EventHandler = `on${Capitalize<EventName>}`;
13// type EventHandler = "onClick" | "onFocus" | "onBlur"
14
15// Praktyczny przykład - CSS units
16type CSSUnit = "px" | "%" | "em" | "rem" | "vh" | "vw";
17type CSSValue = `${number}${CSSUnit}`;
18
19const width: CSSValue = "100px"; // OK
20const height: CSSValue = "50vh"; // OK
21const margin: CSSValue = "2rem"; // OK
22// const padding: CSSValue = "10"; // Błąd! Brakuje jednostki
23
24// Zagnieżdżone template literals
25type BreakpointSize = "sm" | "md" | "lg" | "xl";
26type Breakpoint = `@${BreakpointSize}`;
27type ResponsiveProp<T extends string> = T | `${T}${Breakpoint}`;
28
29type Display = ResponsiveProp<"block" | "none" | "flex">;
30// "block" | "none" | "flex" | "block@sm" | "none@sm" | "flex@sm" |1// Wyciąganie części stringów
2type ExtractId<T> = T extends `id-${infer Id}` ? Id : never;
3
4type UserId = ExtractId<"id-123">; // "123"
5type PostId = ExtractId<"id-abc-def">; // "abc-def"
6type Invalid = ExtractId<"user-123">; // never
7
8// Bardziej złożone pattern matching
9type ParseRoute<T> = T extends `/${infer Module}/${infer Action}`
10 ? { module: Module; action: Action }
11 : never;
12
13type Route1 = ParseRoute<"/users/create">; // { module: "users"; action: "create" }
14type Route2 = ParseRoute<"/posts/edit">; // { module: "posts"; action: "edit" }
15
16// Rekursywne parsowanie
17type Split<S extends string, D extends string> =
18 S extends `${infer T}${D}${infer U}`
19 ? [T, ...Split<U, D>]
20 : [S];
21
22type PathSegments = Split<"users/123/posts/456", "/">;
23// ["users", "123", "posts", "456"]
24
25// Wyciąganie parametrów z URL
26type ExtractParams<T extends string> = T extends `${infer Start}:${infer Param}/${infer Rest}`
27 ? { [K in Param]: string } & ExtractParams<Rest>
28 : T extends `${infer Start}:${infer Param}`
29 ? { [K in Param]: string }
30 : {};
31
32type RouteParams = ExtractParams<"/users/:userId/posts/:postId">;
33// { userId: string; postId: string }1// TypeScript udostępnia wbudowane typy do manipulacji stringami
2type Uppercase<T extends string> = intrinsic;
3type Lowercase<T extends string> = intrinsic;
4type Capitalize<T extends string> = intrinsic;
5type Uncapitalize<T extends string> = intrinsic;
6
7// Przykłady użycia
8type Shout = Uppercase<"hello world">; // "HELLO WORLD"
9type Whisper = Lowercase<"HELLO WORLD">; // "hello world"
10type Title = Capitalize<"typescript">; // "Typescript"
11type CamelCase = Uncapitalize<"TypeScript">; // "typeScript"
12
13// Praktyczne zastosowanie - generowanie event handlers
14type DOMEventName = "click" | "focus" | "blur" | "change" | "submit";
15type EventHandlerName<T extends DOMEventName> = `on${Capitalize<T>}`;
16
17type ClickHandler = EventHandlerName<"click">; // "onClick"
18type FocusHandler = EventHandlerName<"focus">; // "onFocus"
19
20// Automatyczne mapowanie event handlers
21type EventHandlers = {
22 [E in DOMEventName as EventHandlerName<E>]: (event: Event) => void;
23};
24// {
25// onClick: (event: Event) => void;
26// onFocus: (event: Event) => void;
27// onBlur: (event: Event) => void;
28// onChange: (event: Event) => void;
29// onSubmit: (event: Event) => void;
30// }1// Snake case to camel case
2type SnakeToCamelCase<S extends string> = S extends `${infer T}_${infer U}`
3 ? `${T}${Capitalize<SnakeToCamelCase<U>>}`
4 : S;
5
6type CamelCased = SnakeToCamelCase<"hello_world_typescript">; // "helloWorldTypescript"
7
8// Camel case to snake case
9type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
10 ? U extends Uncapitalize<U>
11 ? `${T}${CamelToSnakeCase<U>}`
12 : `${T}_${Lowercase<U>}${CamelToSnakeCase<U>}`
13 : S;
14
15type SnakeCased = CamelToSnakeCase<"helloWorldTypeScript">; // "hello_world_type_script"
16
17// Praktyczne zastosowanie - konwersja API response
18type SnakeToCamelCaseNested<T> = T extends object
19 ? {
20 [K in keyof T as SnakeToCamelCase<K & string>]: SnakeToCamelCaseNested<T[K]>
21 }
22 : T;
23
24interface APIResponse {
25 user_id: number;
26 first_name: string;
27 last_name: string;
28 created_at: string;
29 user_settings: {
30 email_notifications: boolean;
31 dark_mode: boolean;
32 };
33}
34
35type CamelCasedResponse = SnakeToCamelCaseNested<APIResponse>;
36// {
37// userId: number;
38// firstName: string;
39// lastName: string;
40// createdAt: string;
41// userSettings: {
42// emailNotifications: boolean;
43// darkMode: boolean;
44// };
45// }1// Walidacja formatu email
2type ValidateEmail<T extends string> = T extends `${infer Local}@${infer Domain}`
3 ? Domain extends `${infer Name}.${infer Extension}`
4 ? Extension extends "com" | "org" | "net" | "pl" | "edu"
5 ? T
6 : never
7 : never
8 : never;
9
10type ValidEmail = ValidateEmail<"user@example.com">; // "user@example.com"
11type InvalidEmail = ValidateEmail<"invalid-email">; // never
12
13// Walidacja numerów telefonów
14type ValidatePhoneNumber<T extends string> =
15 T extends `+${infer CountryCode}-${infer Rest}`
16 ? CountryCode extends `${number}`
17 ? Rest extends `${number}-${number}-${number}`
18 ? T
19 : never
20 : never
21 : never;
22
23type ValidPhone = ValidatePhoneNumber<"+48-123-456-789">; // "+48-123-456-789"
24type InvalidPhone = ValidatePhoneNumber<"123-456-789">; // never
25
26// Walidacja hex color
27type HexDigit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "A" | "B" | "C" | "D" | "E" | "F" | "a" | "b" | "c" | "d" | "e" | "f";
28type HexColor<T extends string> = T extends `#${HexDigit}${HexDigit}${HexDigit}${infer Rest}`
29 ? Rest extends `${HexDigit}${HexDigit}${HexDigit}`
30 ? T
31 : Rest extends ""
32 ? T
33 : never
34 : never;
35
36type ValidHex1 = HexColor<"#FF5733">; // "#FF5733"
37type ValidHex2 = HexColor<"#F53">; // "#F53"
38type InvalidHex = HexColor<"#GG5733">; // never1// System routingu z type safety
2type RouteDefinition = {
3 "/": {};
4 "/users": {};
5 "/users/:id": { id: string };
6 "/users/:id/posts": { id: string };
7 "/posts/:postId/comments/:commentId": { postId: string; commentId: string };
8};
9
10type ExtractRouteParams<T extends string> =
11 T extends keyof RouteDefinition
12 ? RouteDefinition[T]
13 : T extends `${infer Start}/:${infer Param}/${infer Rest}`
14 ? { [K in Param]: string } & ExtractRouteParams<`${Start}/${Rest}`>
15 : T extends `${infer Start}/:${infer Param}`
16 ? { [K in Param]: string }
17 : {};
18
19// Router z type safety
20class TypedRouter<Routes extends Record<string, any>> {
21 navigate<T extends keyof Routes & string>(
22 path: T,
23 params: Routes[T]
24 ): void {
25 console.log(`Navigating to ${path} with params:`, params);
26 }
27}
28
29const router = new TypedRouter<RouteDefinition>();
30
31router.navigate("/", {}); // OK
32router.navigate("/users/:id", { id: "123" }); // OK
33router.navigate("/posts/:postId/comments/:commentId", { // OK
34 postId: "456",
35 commentId: "789"
36});
37// router.navigate("/users/:id", {}); // Błąd! Brakuje id
38// router.navigate("/users/:id", { id: "123", extra: "field" }); // Błąd! Dodatkowe pole
39
40// Generowanie SQL queries
41type Table = "users" | "posts" | "comments";
42type Column<T extends Table> =
43 T extends "users" ? "id" | "name" | "email" | "created_at" :
44 T extends "posts" ? "id" | "title" | "content" | "author_id" :
45 T extends "comments" ? "id" | "text" | "post_id" | "user_id" :
46 never;
47
48type SelectQuery<T extends Table> = `SELECT ${Column<T> | "*"} FROM ${T}`;
49type WhereClause<T extends Table> = `WHERE ${Column<T>} = ?`;
50type FullQuery<T extends Table> = `${SelectQuery<T>} ${WhereClause<T>}`;
51
52type UserQuery = SelectQuery<"users">;
53// "SELECT id FROM users" | "SELECT name FROM users" | ... | "SELECT * FROM users"
54
55type PostQueryWithWhere = FullQuery<"posts">;
56// "SELECT id FROM posts WHERE id = ?" | "SELECT title FROM posts WHERE title = ?" |1// System design tokens
2type Color = "primary" | "secondary" | "success" | "danger" | "warning";
3type Shade = 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
4type ColorToken = `color-${Color}-${Shade}`;
5
6type Spacing = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 8 | 10 | 12 | 16 | 20 | 24 | 32;
7type SpacingToken = `spacing-${Spacing}`;
8
9type DesignToken = ColorToken | SpacingToken;
10
11// Funkcja pomocnicza z type safety
12function token(tokenName: DesignToken): string {
13 return `var(--${tokenName})`;
14}
15
16const primaryColor = token("color-primary-500"); // OK
17const spacing = token("spacing-4"); // OK
18// const invalid = token("color-invalid-999"); // Błąd!
19
20// Responsive props system
21type Breakpoint = "sm" | "md" | "lg" | "xl";
22type ResponsiveValue<T> = T | { [K in Breakpoint]?: T };
23
24type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse";
25type JustifyContent = "start" | "end" | "center" | "between" | "around" | "evenly";
26
27interface FlexProps {
28 direction?: ResponsiveValue<FlexDirection>;
29 justify?: ResponsiveValue<JustifyContent>;
30 gap?: ResponsiveValue<Spacing>;
31}
32
33// Generowanie klas CSS
34function generateClasses<T extends string>(
35 prefix: string,
36 value: ResponsiveValue<T>
37): string[] {
38 if (typeof value === "object") {
39 return Object.entries(value).map(([breakpoint, val]) =>
40 `${prefix}-${val}@${breakpoint}`
41 );
42 }
43 return [`${prefix}-${value}`];
44}1// Definicja endpointów API
2type APIEndpoints = {
3 "GET /users": { response: User[]; params: {} };
4 "GET /users/:id": { response: User; params: { id: string } };
5 "POST /users": { response: User; params: {}; body: CreateUserDto };
6 "PUT /users/:id": { response: User; params: { id: string }; body: UpdateUserDto };
7 "DELETE /users/:id": { response: void; params: { id: string } };
8};
9
10// Wyciąganie metody i ścieżki
11type ExtractMethod<T> = T extends `${infer Method} ${infer Path}` ? Method : never;
12type ExtractPath<T> = T extends `${infer Method} ${infer Path}` ? Path : never;
13
14// Type-safe API client
15class APIClient {
16 async request<T extends keyof APIEndpoints>(
17 endpoint: T,
18 options: {
19 params?: APIEndpoints[T]["params"];
20 body?: "body" extends keyof APIEndpoints[T] ? APIEndpoints[T]["body"] : never;
21 } = {}
22 ): Promise<APIEndpoints[T]["response"]> {
23 const method = endpoint.split(" ")[0];
24 const path = endpoint.split(" ")[1];
25
26 // Zastąp parametry w ścieżce
27 let url = path;
28 if (options.params) {
29 Object.entries(options.params).forEach(([key, value]) => {
30 url = url.replace(`:${key}`, value as string);
31 });
32 }
33
34 console.log(`${method} ${url}`, options.body);
35
36 // Symulacja API call
37 return {} as APIEndpoints[T]["response"];
38 }
39}
40
41// Użycie
42const api = new APIClient();
43
44api.request("GET /users"); // OK
45api.request("GET /users/:id", { params: { id: "123" } }); // OK
46api.request("POST /users", { body: { /* ... */ } }); // OK
47// api.request("GET /users/:id"); // Błąd! Brakuje params
48// api.request("GET /users", { body: {} }); // Błąd! GET nie ma body1// System tłumaczeń z zagnieżdżonymi kluczami
2type TranslationKeys = {
3 common: {
4 yes: string;
5 no: string;
6 cancel: string;
7 };
8 user: {
9 profile: {
10 title: string;
11 edit: string;
12 };
13 settings: {
14 title: string;
15 notifications: {
16 email: string;
17 push: string;
18 };
19 };
20 };
21};
22
23// Generowanie ścieżek do kluczy
24type PathsToStringProps<T> = T extends string
25 ? []
26 : {
27 [K in keyof T]: [K, ...PathsToStringProps<T[K]>];
28 }[keyof T];
29
30type TranslationPath = PathsToStringProps<TranslationKeys>;
31// ["common", "yes"] | ["common", "no"] | ["user", "profile", "title"] |
32// Konwersja tablicy do stringa z kropkami
33type JoinPath<T extends string[]> = T extends []
34 ? ""
35 : T extends [infer First]
36 ? First
37 : T extends [infer First, ...infer Rest]
38 ? First extends string
39 ? Rest extends string[]
40 ? `${First}.${JoinPath<Rest>}`
41 : never
42 : never
43 : never;
44
45type TranslationKey = JoinPath<TranslationPath>;
46// "common.yes" | "common.no" | "user.profile.title" |
47// Funkcja tłumaczenia z type safety
48function t(key: TranslationKey): string {
49 // Implementacja pobierania tłumaczenia
50 return key;
51}
52
53const title = t("user.profile.title"); // OK
54const email = t("user.settings.notifications.email"); // OK
55// const invalid = t("user.invalid.key"); // Błąd!
56
57// Tłumaczenia z parametrami
58type TranslationWithParams = {
59 "welcome": { name: string };
60 "items_count": { count: number };
61 "date_format": { date: Date };
62};
63
64function tWithParams<K extends keyof TranslationWithParams>(
65 key: K,
66 params: TranslationWithParams[K]
67): string {
68 // Implementacja z podstawianiem parametrów
69 return `${key} with ${JSON.stringify(params)}`;
70}
71
72tWithParams("welcome", { name: "Jan" }); // OK
73tWithParams("items_count", { count: 5 }); // OK
74// tWithParams("welcome", { count: 5 }); // Błąd! Nieprawidłowe parametryTemplate literal types to potężne narzędzie, które przenosi manipulację stringami na poziom systemu typów. Pozwala to na tworzenie bardziej ekspresywnych i bezpiecznych API, szczególnie w kontekście routingu, internacjonalizacji i systemów stylowania.