Литеральные типы
Литеральные типы — конкретные значения как типы: не просто string, а именно "hello"; не number, а именно 42.
Зачем нужно
- Ограничение допустимых значений до конкретного набора
- Более точная типизация:
"GET" | "POST"вместоstring - Основа для discriminated unions и pattern matching
- Template literal types — мощные строковые паттерны на уровне типов
Где используется
- HTTP-методы:
"GET" | "POST" | "PUT" | "DELETE" - Событийные системы: конкретные имена событий
- Конфигурации: ограниченный набор опций
- API: точное описание допустимых параметров
Предпосылки
- Примитивные типы — string, number, boolean
- Union и Intersection — объединение литералов в union
String Literal Types
// Тип — конкретная строка
type Direction = "up" | "down" | "left" | "right";
let dir: Direction = "up"; // OK
dir = "down"; // OK
dir = "diagonal"; // Ошибка! Type '"diagonal"' is not assignable
// Параметры функций
function move(direction: "up" | "down" | "left" | "right"): void {
console.log(`Moving ${direction}`);
}
move("up"); // OK
move("UP"); // Ошибка! Регистр имеет значение
// HTTP-методы
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
function fetch(url: string, method: HttpMethod): void {
// ...
}
Number Literal Types
// Конкретные числа
type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice: DiceRoll {
return (Math.floor(Math.random * 6) + 1) as DiceRoll;
}
// Битовые значения
type BitFlag = 0 | 1;
// Порты
type CommonPort = 80 | 443 | 3000 | 8080;
Boolean Literal Types
// true или false как тип
type True = true;
type False = false;
// Полезно в generics
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
// Объект с флагом
interface Config {
debug: true; // Только true!
}
as const
as const сужает тип до максимально конкретного литерального типа:
// Без as const
let method = "GET"; // string
const method2 = "GET"; // "GET" (const сужает)
// as const для объектов
const config = {
url: "https://api.example.com",
method: "GET",
timeout: 5000,
};
// Тип: { url: string; method: string; timeout: number }
const config = {
url: "https://api.example.com",
method: "GET",
timeout: 5000,
} as const;
// Тип: {
// readonly url: "https://api.example.com";
// readonly method: "GET";
// readonly timeout: 5000;
// }
// as const для массивов
const methods = ["GET", "POST", "PUT"] as const;
// Тип: readonly ["GET", "POST", "PUT"]
// Извлечение union из as const массива
type Method = (typeof methods)[number]; // "GET" | "POST" | "PUT"
as const и функции
// Проблема: литеральный тип теряется при передаче в функцию
function request(method: "GET" | "POST", url: string): void {}
const options = { method: "GET", url: "/api" };
request(options.method, options.url);
// Ошибка! options.method — string, а не "GET"
// Решение 1: as const на объекте
const options = { method: "GET", url: "/api" } as const;
request(options.method, options.url); // OK
// Решение 2: as const на значении
const options = { method: "GET" as const, url: "/api" };
request(options.method, options.url); // OK
// Решение 3: satisfies (TypeScript 5.0+)
const options = {
method: "GET",
url: "/api",
} as const satisfies { method: "GET" | "POST"; url: string };
Template Literal Types
Строковые шаблоны на уровне типов (TypeScript 4.1+):
// Базовый синтаксис
type Greeting = `Hello, ${string}`;
let g: Greeting = "Hello, World"; // OK
let g2: Greeting = "Hi, World"; // Ошибка!
// Комбинация литералов
type Size = "small" | "medium" | "large";
type Color = "red" | "blue" | "green";
type ColoredSize = `${Color}-${Size}`;
// "red-small" | "red-medium" | "red-large" |
// "blue-small" | "blue-medium" | "blue-large" |
// "green-small" | "green-medium" | "green-large"
// Event handlers
type EventName = "click" | "focus" | "blur";
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"
CSS-подобные типы
type CSSUnit = "px" | "em" | "rem" | "%";
type CSSValue = `${number}${CSSUnit}`;
let width: CSSValue = "100px"; // OK
let height: CSSValue = "50%"; // OK
let bad: CSSValue = "100"; // Ошибка!
let bad2: CSSValue = "wide"; // Ошибка!
Utility Types для строк
TypeScript имеет встроенные типы для преобразования строк:
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
// Комбинирование
type Event = "click" | "scroll" | "resize";
type OnEvent = `on${Capitalize<Event>}`;
// "onClick" | "onScroll" | "onResize"
// Getter/Setter типы
type Prop = "name" | "age" | "email";
type Getter = `get${Capitalize<Prop>}`;
// "getName" | "getAge" | "getEmail"
type Setter = `set${Capitalize<Prop>}`;
// "setName" | "setAge" | "setEmail"
Template Literal Types с infer
// Извлечение частей строки
type ExtractId<S extends string> = S extends `user_${infer Id}` ? Id : never;
type UserId = ExtractId<"user_123">; // "123"
type Invalid = ExtractId<"admin_456">; // never
// Парсинг путей
type ExtractParams<Path extends string> =
Path extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractParams<Rest>
: Path extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractParams<"/users/:userId/posts/:postId">;
// "userId" | "postId"
Паттерн: Создание типов из данных
// Массив допустимых значений + тип из него
const ROLES = ["admin", "user", "guest"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "user" | "guest"
// Проверка в рантайме
function isRole(value: string): value is Role {
return (ROLES as readonly string).includes(value);
}
// Объект + тип из ключей/значений
const STATUS_MAP = {
active: "ACTIVE",
inactive: "INACTIVE",
banned: "BANNED",
} as const;
type StatusKey = keyof typeof STATUS_MAP;
// "active" | "inactive" | "banned"
type StatusValue = (typeof STATUS_MAP)[StatusKey];
// "ACTIVE" | "INACTIVE" | "BANNED"
Частые ошибки
- let vs const —
letрасширяет тип,constсужает до литерала
const x = "hello"; // тип: "hello"
let y = "hello"; // тип: string
// Для let нужна явная аннотация
let z: "hello" | "world" = "hello";
- Потеря литерального типа при передаче — объект без
as constрасширяет типы - Слишком большой union из template literals — может замедлить компиляцию
// Осторожно: 10 * 10 * 10 = 1000 вариантов
type Big = `${0|1|2|3|4|5|6|7|8|9}${0|1|2|3|4|5|6|7|8|9}${0|1|2|3|4|5|6|7|8|9}`;
- Забыть
as constпри извлечении union из массива — без него будет простоstring
Практика
- Создайте тип
HttpMethodиз литералов и функцию принимающую только эти значения - Используйте
as constдля объекта конфигурации и извлеките типы - Создайте template literal type для CSS-значений:
"${number}px"или"${number}%" - Напишите тип
EventHandler<E>который превращает"click"в"onClick" - Создайте массив
as constи извлеките union type из его элементов
Связанные темы
- Union и Intersection — комбинирование литералов
- as const и Enum — сравнение подходов
- Template literal types — подробнее про шаблонные строковые типы
- Conditional types — условные типы с infer