ПрограммированиеFrontend разработчик

Как работает типизация константных объектов со свойством as const? Какие преимущества и какие проблемы бывают при неправильной типизации таких объектов?

Проходите собеседования с ИИ помощником Hintsage

Ответ

as const в TypeScript превращает значения и свойства объекта/массива в readonly и литеральные типы. Это даёт строгую типизацию: значения не "расширяются" до базового типа, а сохраняют своё конкретное значение.

Пример:

const a = { status: 'success' }; // Тип: { status: string } const b = { status: 'success' } as const; // Тип: { readonly status: "success" }

Преимущества:

  • Строгая типизация (например, switch/case по строкам, списки констант)
  • Идеально для enums, actionTypes в Redux, списков ролей и т.д.
  • Предотвращает случайное изменение значений

Недостатки/Особенности:

  • Свойства делают readonly, попытка изменить их вызовет ошибку.
  • Невнимательное использование может привести к несовместимости типов (например, если нужна строка, а вы передали литерал).

Вопрос с подвохом

Вопрос: Если использовать as const для массива из строк, какой у него будет тип, и можно ли будет его передать как обычный string[]?

Ответ: Тип будет readonly ["a", "b", "c"], то есть кортеж из литеральных типов с readonly-ограничением. Такой массив несовместим c типом string[]; его нельзя напрямую передать туда, где ожидают изменяемый массив строк.

const arr = ['a', 'b', 'c'] as const; // readonly ["a", "b", "c"] function acceptsStrings(x: string[]) {} acceptsStrings(arr); // Ошибка! Тип несовместим

Примеры реальных ошибок из-за незнания тонкостей темы


История

Проект: Хранили список actionTypes как константный массив c as const, затем пытались передавать его в функцию ожидания string[]. Получили ошибку типов, пришлось явно преобразовывать с помощью .slice() или [...arr].


История

Проект: В Redux-микрофреймворке тип action.type определяли как литерал через as const. При реализации switch-case забыли про строгую типизацию, не обрабатывали все возможные литералы, из-за чего не сработал exhaustive check — ошибка не проявилась, пока не добавили новый экшн.


История

Проект: При авто-генерации API-эндпоинтов описывали их ключи через as const-массив. Попытка использовать эти ключи как индекс обычного Record<string, ...> провалилась из-за рассогласования типов — пришлось добавлять преобразование, либо в явном виде использовать типы ключей из массива.