Что такое template literal types в TypeScript?
Template Literal Types в TypeScript
Template literal types появились в TypeScript 4.1 и позволяют создавать новые строковые типы путём комбинирования строк и других типов, используя синтаксис, идентичный JavaScript template literals.
Базовый синтаксис
Простейший случай — конкатенация строкового литерала с другим типом:
type Greeting = `Hello, ${string}`;
// Принимает любую строку, начинающуюся с 'Hello, '
type Direction = 'left' | 'right' | 'top' | 'bottom';
type CSSProperty = `margin-${Direction}`;
// 'margin-left' | 'margin-right' | 'margin-top' | 'margin-bottom'
Дистрибутивность по union
Ключевая особенность: TypeScript автоматически распределяет template literal type по всем членам union-типа, создавая декартово произведение:
type Color = 'red' | 'blue';
type Size = 'sm' | 'lg';
type ClassName = `${Color}-${Size}`;
// 'red-sm' | 'red-lg' | 'blue-sm' | 'blue-lg'
Встроенные utility-типы для работы со строками
Вместе с template literal types появились встроенные манипулятивные типы:
type Upper = Uppercase<'hello'>; // 'HELLO'
type Lower = Lowercase<'WORLD'>; // 'world'
type Cap = Capitalize<'foo'>; // 'Foo'
type Uncap = Uncapitalize<'Bar'>; // 'bar'
Практический паттерн: генерация event-имён
Один из самых распространённых паттернов — автоматическая генерация типов для обработчиков событий:
type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur'
// Создаём объект с типизированными обработчиками
type Handlers = {
[K in EventHandler]?: () => void;
};
Извлечение частей строкового типа через infer
Template literal types работают вместе с infer в conditional types, позволяя парсить строковые типы:
type ExtractRoute<T extends string> =
T extends `/api/${infer Resource}/${infer Id}`
? { resource: Resource; id: Id }
: never;
type Parsed = ExtractRoute<'/api/users/123'>;
// { resource: 'users'; id: '123' }
Реальный use-case: типобезопасный доступ к вложенным полям
type Join<K, P> = K extends string
? P extends string
? `${K}.${P}`
: never
: never;
// Генерация всех путей объекта (упрощённо)
type Paths<T> = T extends object
? { [K in keyof T]: K extends string
? T[K] extends object
? K | Join<K, Paths<T[K]>>
: K
: never
}[keyof T]
: never;
type Config = { db: { host: string; port: number }; debug: boolean };
type AllPaths = Paths<Config>;
// 'db' | 'debug' | 'db.host' | 'db.port'
Ограничения
Template literal types вычисляются статически, поэтому сложные рекурсивные конструкции могут вызывать ошибку Type instantiation is excessively deep. Также они работают только со строками, числами, булевыми значениями, bigint, null и undefined — объекты напрямую не поддерживаются внутри ${}.
Что хочет услышать интервьюер
Понимание синтаксиса и аналогии с JavaScript template literals
Знание дистрибутивности по union-типам и декартового произведения
Знакомство со встроенными строковыми утилитами: Uppercase, Lowercase, Capitalize, Uncapitalize
Практические примеры применения: генерация event-имён, CSS-классов, путей к полям объекта
Умение комбинировать template literal types с infer для парсинга строковых типов
Пример: Базовая дистрибутивность по union
type Axis = 'x' | 'y';
type PaddingProp = `padding-${Axis}`;
// 'padding-x' | 'padding-y'
const apply = (prop: PaddingProp, value: number) => ({ [prop]: value });
Пример: Генерация типизированных event-обработчиков
type DOMEvent = 'click' | 'focus' | 'keydown';
type HandlerKey = `on${Capitalize<DOMEvent>}`;
// 'onClick' | 'onFocus' | 'onKeydown'
type ComponentProps = {
[K in HandlerKey]?: (e: Event) => void;
};
// Теперь TypeScript подскажет допустимые имена обработчиков
const props: ComponentProps = {
onClick: (e) => console.log(e),
};
Пример: Парсинг строковых типов через infer
// Извлекаем метод и путь из строки вида 'GET /users'
type ParseEndpoint<T extends string> =
T extends `${infer Method} /${infer Path}`
? { method: Uppercase<Method>; path: Path }
: never;
type Endpoint = ParseEndpoint<'get /users'>;
// { method: 'GET'; path: 'users' }
Типичные ошибки
Путают с обычными template literals в JavaScript — не понимают, что это конструкция системы типов, а не runtime
Не знают о дистрибутивности по union и удивляются, что TypeScript сам строит декартово произведение
Забывают про встроенные утилиты Capitalize/Uppercase и пытаются реализовать их вручную
Не используют infer внутри template literal types для извлечения подстрок
Создают излишне сложные рекурсивные типы, получая ошибку 'Type instantiation is excessively deep'


