Новый синтаксис const-параметров типа в TypeScript позволяет функции сохранять литеральные типы нетронутыми, не заставляя вызывающий код повсеместно использовать as const, что устраняет наиболее распространенный источник ошибок расширения типов (type-widening).
Проблема расширения типов, преследующая обобщенный код
Когда обобщенная функция получает объектный литерал, компилятор расширяет любой литеральный параметр до его более широкого примитивного типа.
function call<T>(arg: T) {}
call({ method: "GET" }) // T is inferred as { method: string }
Литерал "GET" превращается в string. Код, зависящий от точного значения — например, связанные объединения (discriminated unions) или извлечение через шаблонные литералы — ломается, так как тип больше не содержит точного литерала. Разработчики давно обходили это, записывая { method: "GET" } as const в месте вызова, что указывает компилятору сохранить литерал, но это исправление находится в руках вызывающей стороны, а не в определении функции.
Const-параметры типа: исправление на уровне сигнатуры
Новый модификатор const для параметра типа указывает компилятору выводить наиболее узкий возможный тип для этого обобщенного аргумента. Объявление функции как function foo<const T>(arg: T) заставляет T автоматически вести себя так, будто вызывающая сторона написала as const.
- Литералы string, number, boolean сохраняют свои точные значения (
"GET"вместоstring). - Массивы становятся readonly-кортежами, где каждый элемент имеет точный тип.
- Объекты превращаются в глубоко readonly-структуры, сохраняя литеральные типы на каждом уровне вложенности.
Поскольку ограничение прописано в сигнатуре функции, каждый вызывающий код получает преимущество автоматически; забытый каст (приведение типа) больше не ведет к небезопасности типов.
Почему это лучше классического хака с as const
as const — это решение на стороне вызывающего кода. Оно требует, чтобы каждый потребитель обобщенной функции не забывал добавлять утверждение типа (assertion). Пропустите хотя бы один вызов — и типобезопасность испарится. Const-параметр типа переносит ответственность непосредственно в дизайн API: функция заявляет: «Мне нужна максимально узкая форма того, что вы передадите», и компилятор это обеспечивает.
Этот сдвиг наиболее важен для библиотек и утилит, предоставляющих обобщенные билдеры, фабрики конфигураций или любые API, где литеральное значение поля определяет логику типов. Автор библиотеки может гарантировать правильный вывод типов, не контролируя код потребителей.
Сценарии из реальной практики, которым это выгодно
- Билдеры конфигураций — имена окружений (
"dev" | "prod") остаются литералами, что позволяет выполнять проверки через связанные объединения без дополнительных кастов. - Определения API-роутов — строки путей остаются точными, позволяя типам на основе шаблонных литералов извлекать параметры (
"/users/:id"→\/users/${string}``). - Хелперы для конечных автоматов — идентификаторы состояний остаются фиксированными литералами при цепочке вызовов методов, что предотвращает случайные несоответствия состояний.
В каждом из этих случаев const-параметр устраняет повторяющийся шаблонный код as const и снижает вероятность появления трудноуловимых багов.
Сочетание с оператором satisfies
Оператор satisfies проверяет, соответствует ли значение структурному типу, сохраняя при этом его исходную литеральную информацию. Использование их вместе дает лучшее из обоих миров: const-параметры обеспечивают узкий вывод типов, а satisfies гарантирует, что значение соответствует требуемой форме.
function makeConfig<const C>(cfg: C) {
// cfg is inferred with exact literals
}
const cfg = {
env: "staging",
ports: [8080, 8443],
} satisfies { env: string; ports: number[] };
makeConfig(cfg); // works, literals stay intact
Когда стоит оставить as const
Const-параметр незаменим, когда вы контролируете сигнатуру функции. Если вы работаете со сторонними функциями, в которых нет этого модификатора, или вам нужно разово сохранить литеральный тип локальной переменной, as const остается правильным инструментом. Он по-прежнему служит основным способом «заморозить» значение, не изменяя вызываемый API.
На что обратить внимание в будущем
Эта функция еще совсем новая, поэтому инструменты и паттерны сообщества продолжают развиваться. Ожидайте обновлений поддержки IDE, которые добавят новый синтаксис в автодополнение и предложения быстрых исправлений (quick-fix). Следите за мейнтейнерами библиотек: многие начнут переводить публичные обобщенные типы на const-параметры, что может привести к breaking changes в коде, который ранее полагался на явные касты as const.
Итог: Встраивая сохранение литералов непосредственно в параметры типа функции, const-параметры TypeScript устраняют распространенный источник ошибок расширения типов и перекладывают ответственность за безопасность с вызывающей стороны обратно на проектировщика API. Используйте их для любых обобщенных точек входа, которыми вы управляете; оставляйте as const для локальных значений или внешних API.
