Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Літеральні типи в TypeScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Literal types** (літеральні типи) у TypeScript обмежують змінну конкретними значеннями, як `"up"` або `404`, а не широкими типами на зразок `string` або `number`. ```typescript type Status = "loading" | "success" | "error"; function setStatus(s: Status) {} setStatus("banana"); // Помилка - допустимі тільки три вказані значення setStatus("success"); // OK ``` **Ключове:** неправильні значення відловлюються на етапі компіляції, не в runtime.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Literal types** (літеральні типи) у TypeScript фіксують змінну на точних значеннях - як `"up"` або `404` - а не на широких категоріях типу `string` або `number`. ## Теорія ### TL;DR - Literal type - як іменне паркомісце: підходить тільки `"up"`, не будь-який `string` - Головна різниця: `string` прийме `"banana"`, а `"up" | "down"` - ні, компілятор зупинить на етапі перевірки - Використовуй, коли варіантів 3-10 і всі відомі на етапі розробки - TypeScript розширює літерали в деяких контекстах (масиви, прості об'єкти), якщо не використовувати `as const` ### Швидкий приклад ```typescript // Без literal - приймає будь-який рядок type Status = string; function setStatus(s: Status) {} setStatus("banana"); // Помилки немає // З literal - тільки точні значення type Status = "loading" | "success" | "error"; function setStatus(s: Status) {} setStatus("banana"); // Помилка: '"banana"' is not assignable to type 'Status' setStatus("success"); // OK ``` Компілятор перевіряє точний токен, а не просто категорію типу. Неправильні значення блокуються до запуску. ### Ключова різниця Тип `string` приймає будь-яку послідовність символів. `"loading" | "success" | "error"` приймає тільки ці три токени. Ти обмінюєш гнучкість на гарантію. TypeScript перевіряє точне значення під час аналізу типів і видаляє все при компіляції, тому JavaScript бачить звичайні рядки. Накладних витрат у runtime немає. Одна річ, яка дивує більшість розробників вперше: TypeScript автоматично розширює літерали в деяких контекстах. Напиши `{ dir: "up" }` - і TypeScript виведе `{ dir: string }`, а не `{ dir: "up" }`. Літерал губиться. Щоб зберегти його, використовуй `as const`. ### Коли використовувати - Стани UI: `type Status = "draft" | "published" | "archived"` - HTTP-коди: `type Code = 200 | 201 | 404` - Ключі вирівнювання і конфігурації: `type Align = "left" | "center" | "right"` - Не варто для призначеного для користувача вводу (email, ім'я) - використовуй `string` - Не варто для булевих флагів - використовуй `boolean`, хіба що потрібен тільки `true` ### as const: фіксування літералів ```typescript // Без as const - TypeScript розширює до string[] const directions = ["up", "down"]; type Dir = typeof directions[0]; // string, не "up" // З as const - залишається readonly ["up", "down"] const directions = ["up", "down"] as const; type Dir = typeof directions[number]; // "up" | "down" ``` Це важливо, коли ти виводиш типи з масивів або об'єктів конфігурації в runtime. ### Як це обробляє компілятор TypeScript трактує literal types як брендовані примітиви під час перевірки типів. Він зіставляє точний токен `"up"` з членами union за структурною еквівалентністю. На виході (emit) літерали стають звичайними JS-значеннями - Node.js і браузер бачать тільки сирі рядки або числа. Без накладних витрат. Розширення (widening) відбувається в коваріантних контекстах (масиви, типи, що повертаються), якщо `as const` не фіксує виведення. ### Типові помилки **Забули `as const` на масиві** ```typescript const roles = ["admin", "user"]; // виводить string[] type Role = typeof roles[number]; // string, не "admin" | "user" // Виправлення: const roles = ["admin", "user"] as const; type Role = typeof roles[number]; // "admin" | "user" ``` **Припущення, що literal types перевіряють значення в runtime** ```typescript function validate(code: 200 | 404) { return true; } // TypeScript видаляє типи при компіляції. // JS бачить: function validate(code) { return true; } // Виправлення: використовуй Zod: z.union([z.literal(200), z.literal(404)]) ``` **Розширення типу, що повертається** ```typescript // Повертає string, а не "red" | "green" function getColor(type: "error" | "success"): string { return type === "error" ? "red" : "green"; } // Виправлення: зафіксуй тип повернення function getColor(type: "error" | "success"): "red" | "green" { return type === "error" ? "red" : "green"; } ``` Якщо значення приходить з користувацького вводу або бази даних, в runtime це `string` незалежно від literal type. Використовуй type guard або Zod для перевірки. ### Де зустрічається в реальному коді - React / shadcn-ui: `variant: "primary" | "outline"` в пропсах кнопки - tRPC: `type Method = "GET" | "POST"` у визначеннях маршрутів - TanStack Query: `"pending" | "success" | "error"` для статусів запитів - Zod: `z.literal("published")` у схемах для валідації API ### Питання на співбесіді **Q:** Яка різниця між `type Foo = "a"` і `const foo = "a" as const`? **A:** `type` визначає псевдонім типу, який існує тільки на рівні типів і зникає при компіляції. `as const` створює const-змінну, чий виведений тип - літерал `"a"`. Обидва дають тип `"a"`, але `as const` також дає значення, доступне в runtime. **Q:** Чи можна використовувати literal types з generics? **A:** Так. `function identity<T extends "foo" | "bar">(x: T): T` обмежує generic літеральним union. Викликач отримує назад точний літерал, а не розширений тип. **Q:** Коли краще обрати enum замість literal union? **A:** Literal union компілюється в ніщо і простіший у написанні. Enum генерує об'єкт в runtime із зворотним маппінгом (число до імені). В більшості сучасних TypeScript-проектів literal union достатньо. **Q:** Як discriminated unions (дискриміновані об'єднання) використовують literal types? **A:** Літеральне поле слугує дискримінантом. TypeScript автоматично звужує тип у кожній гілці. ```typescript type ApiResponse<T> = | { status: "success"; data: T } | { status: "error"; error: string }; // В обробнику: if (response.status === "success") { console.log(response.data); // TypeScript знає, що data тут є } ``` ## Приклади ### Базовий: обробник напрямків ```typescript type Direction = "up" | "down" | "left" | "right"; function move(dir: Direction, distance: number) { console.log(`Move ${distance}px ${dir}`); } move("up", 10); // OK - виводить "Move 10px up" move("left", 5); // OK move("diagonal", 3); // Помилка: '"diagonal"' is not assignable to type 'Direction' ``` TypeScript відловлює неправильне значення до того, як код потрапить на продакшен. ### Проміжний: компонент кнопки ```typescript type ButtonVariant = "primary" | "secondary" | "danger"; interface ButtonProps { variant: ButtonVariant; label: string; } function Button({ variant, label }: ButtonProps) { return `<button class="btn btn-${variant}">${label}</button>`; } Button({ variant: "primary", label: "Зберегти" }); // OK - рендерить btn-primary Button({ variant: "warning", label: "Зберегти" }); // Помилка: '"warning"' is not assignable ``` Компонент приймає тільки ті варіанти, для яких є CSS-класи. Жодних сюрпризів у runtime.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.