Что такое template literal types в TypeScript?

SeniorTypeScript · Frontend·Обновлено 15 июля 2026
Коротко
Template literal types — это типы TypeScript, построенные по аналогии с template literal строками, позволяющие конструировать новые строковые типы через конкатенацию и манипуляцию над union-типами на уровне системы типов.

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'

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее