Литеральные типы в TypeScript позволяют указывать точные значения, которые могут храниться в переменных, обеспечивая большую точность по сравнению с широкими типами, такими как string или number.
Они служат основой для создания чётких и защищённых типов приложений.
Ключевые концепции
- Строковые литералы: Точные строковые значения, например,
"success" | "error" - Числовые литералы: Конкретные числовые значения, например,
1 | 2 | 3 - Булевые литералы: Только
trueилиfalse - Шаблонные литеральные типы: Строковые литеральные типы, созданные с использованием синтаксиса шаблонов строк
Распространённые сценарии использования
- Определение конкретных наборов допустимых значений
- Создание дискриминантных объединений
- Типобезопасная обработка событий
- Типизация ответов API
- Объекты конфигурации
Строковые литеральные типы
Строковый литеральный тип представляет точное строковое значение:
Пример
// Переменная со строковым литеральным типом
let direction: "north" | "south" | "east" | "west";
// Допустимые присваивания
direction = "north";
direction = "south";
// Недопустимые присваивания вызвали бы ошибки
// direction = "northeast"; // Ошибка: Тип '"northeast"' не может быть присвоен типу '"north" | "south" | "east" | "west"'
// direction = "up"; // Ошибка: Тип '"up"' не может быть присвоен типу '"north" | "south" | "east" | "west"'
// Использование строкового литерального типа в функциях
function move(direction: "north" | "south" | "east" | "west") {
console.log(`Перемещение в направлении ${direction}`);
}
move("east"); // Правильно
// move("up"); // Ошибка: Аргумент типа '"up"' не может быть присвоен параметру типа...
Числовые литеральные типы
Подобно строковым литералам, числовые литеральные типы представляют конкретные числовые значения:
Пример
// Переменная с числовым литеральным типом
let diceRoll: 1 | 2 | 3 | 4 | 5 | 6;
// Допустимые присваивания
diceRoll = 1;
diceRoll = 6;
// Недопустимые присваивания вызвали бы ошибки
// diceRoll = 0; // Ошибка: Тип '0' не может быть присвоен типу '1 | 2 | 3 | 4 | 5 | 6'
// diceRoll = 7; // Ошибка: Тип '7' не может быть присвоен типу '1 | 2 | 3 | 4 | 5 | 6'
// diceRoll = 2.5; // Ошибка: Тип '2.5' не может быть присвоен типу '1 | 2 | 3 | 4 | 5 | 6'
// Использование числового литерального типа в функциях
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return Math.floor(Math.random() * 6) + 1 as 1 | 2 | 3 | 4 | 5 | 6;
}
const result = rollDice();
console.log(`Вы выбросили ${result}`);
Булевые литеральные типы
Булевые литеральные типы используются реже, поскольку существует всего два возможных значения, однако в некоторых случаях они могут быть полезны:
Пример
// Тип, который может быть только литеральным значением 'true'
type YesOnly = true;
// Функция, которая должна вернуть true
function alwaysSucceed(): true {
// Всегда возвращает литеральное значение 'true'
return true;
}
// Объединение булевого литерала с другими типами
type SuccessFlag = true | "success" | 1;
type FailureFlag = false | "failure" | 0;
function processResult(result: SuccessFlag | FailureFlag) {
if (result === true || result === "success" || result === 1) {
console.log("Операция выполнена успешно");
} else {
console.log("Операция завершилась неудачно");
}
}
processResult(true); // "Операция выполнена успешно"
processResult("success"); // "Операция выполнена успешно"
processResult(1); // "Операция выполнена успешно"
processResult(false); // "Операция завершилась неудачно"
Объектные типы с литералами
Литеральные типы могут комбинироваться с объектными типами для создания чрезвычайно специфичной формы:
Пример
// Объект с литеральными свойствами
type HTTPSuccess = {
status: 200 | 201 | 204;
statusText: "OK" | "Created" | "No Content";
data: any;
};
type HTTPError = {
status: 400 | 401 | 403 | 404 | 500;
statusText: "Bad Request" | "Unauthorized" | "Forbidden" | "Not Found" | "Internal Server Error";
error: string;
};
type HTTPResponse = HTTPSuccess | HTTPError;
function handleResponse(response: HTTPResponse) {
if (response.status >= 200 && response.status < 300) {
console.log(`Успех: ${response.statusText}`);
console.log(response.data);
} else {
console.log(`Ошибка ${response.status}: ${response.statusText}`);
console.log(`Сообщение: ${response.error}`);
}
}
// Пример использования
const successResponse: HTTPSuccess = {
status: 200,
statusText: "OK",
data: { username: "john_doe", email: "john@example.com" }
};
const errorResponse: HTTPError = {
status: 404,
statusText: "Not Found",
error: "Пользователь не найден в базе данных"
};
handleResponse(successResponse);
handleResponse(errorResponse);
Шаблонные литеральные типы
Начиная с TypeScript 4.1+, были введены шаблонные литеральные типы, которые позволяют создавать новые строковые литеральные типы путем комбинации существующих с использованием синтаксиса шаблонов строк:
Пример
// Базовые шаблонные литералы
type Direction = "north" | "south" | "east" | "west";
type Distance = "1km" | "5km" | "10km";
// Использование шаблонных литералов в сочетании друг с другом
type DirectionAndDistance = `${Direction}-${Distance}`;
// "north-1km" | "north-5km" | "north-10km" | "south-1km" | ...
let route: DirectionAndDistance;
route = "north-5km"; // Правильно
route = "west-10km"; // Правильно
// route = "north-2km"; // Ошибка
// route = "5km-north"; // Ошибка
// Продвинутая манипуляции со строками
type EventType = "click" | "hover" | "scroll";
type EventTarget = "button" | "link" | "div";
type EventName = `on${Capitalize<EventType>}${Capitalize<EventTarget>}`;
// "onClickButton" | "onClickLink" | "onClickDiv" | ...
// Доступ к динамическим свойствам
type User = {
id: number;
name: string;
email: string;
createdAt: Date;
};
type GetterName<T> = `get${Capitalize<string & keyof T>}`;
type UserGetters = {
[K in keyof User as GetterName<User>]: () => User[K];
};
// { getId: () => number; getName: () => string; ... }
// Работа с шаблоном строк
type ExtractRouteParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? Param | ExtractRouteParams<Rest>
: T extends `${string}:${infer Param}`
? Param
: never;
type Params = ExtractRouteParams<"/users/:userId/posts/:postId">; // "userId" | "postId"
// Единицы измерения CSS и значения
type CssUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw';
type CssValue = `${number}${CssUnit}`;
let width: CssValue = '100px'; // Правильно
let height: CssValue = '50%'; // Правильно
// let margin: CssValue = '10'; // Ошибка
// let padding: CssValue = '2ex'; // Ошибка
// Версии API
type ApiVersion = 'v1' | 'v2' | 'v3';
type Endpoint = 'users' | 'products' | 'orders';
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiUrl = `https://api.example.com/${ApiVersion}/${Endpoint}`;
// Сложный пример: Динамический генератор SQL-запросов
type Table = 'users' | 'products' | 'orders';
type Column<T extends Table> =
T extends 'users' ? 'id' | 'name' | 'email' | 'created_at' :
T extends 'products' ? 'id' | 'name' | 'price' | 'in_stock' :
T extends 'orders' ? 'id' | 'user_id' | 'total' | 'status' : never;
type WhereCondition<T extends Table> = {
[K in Column<T>]?: {
equals?: any;
notEquals?: any;
in?: any[];
};
};
function query<T extends Table>(
table: T,
where?: WhereCondition<T>
): `SELECT * FROM ${T}${string}` {
// Реализация строила бы запрос
return `SELECT * FROM ${table}` as const;
}
// Использование
const userQuery = query('users', {
name: { equals: 'John' },
created_at: { in: ['2023-01-01', '2023-12-31'] }
});
// Тип: "SELECT * FROM users WHERE ..."
Лучшие практики
Да:
- Используйте литеральные типы для фиксированных наборов значений (перечисления, варианты конфигурации)
- Комбинируйте с объединёнными типами для повышения уровня типобезопасности
- Используйте шаблонные литеральные типы для сопоставления строковых паттернов
- По возможности полагайтесь на автоматический вывод типов
- Документально поясняйте смысл литеральных типов
Нет:
- Не используйте слишком много литеральных типов, когда общий тип подошёл бы лучше
- Не создавайте чрезмерно крупных объединений, так как это влияет на производительность
- Не используйте строковые литералы, когда больше подходит перечисление
Вопросы производительности
Производительность проверки типов
- Большие объединения типов могут замедлять проверку типов
- Сложные шаблонные литеральные типы увеличивают время компиляции
- Рассмотрите возможность использования псевдонимов типов для сложных литеральных типов
- Учитывайте ограничения глубины рекурсии TypeScript