Перейти к содержанию

Типизация as const

В TypeScript as const позволяет фиксировать значения литералов и делать их максимально конкретными: строки не расширяются до string, числа — до number, а свойства объектов и массивы становятся readonly. Это удобно, когда нужно работать с неизменяемыми объектами или использовать их как "enum-подобные" конструкции. Документация: const assertions

Посмотрим, как это работает на практике и какие подводные камни возникают при использовании обычной типизации и as const.

Примеры

Прямое указание типа

ts
type Fruit = { name: string };

const Apple: Fruit = { name: "Apple" }; // создаём объект Apple и указываем, что он имеет тип Fruit
Apple.name = "Orange"; // ⚠️ нет ошибки

Свойство остаётся изменяемым. Видно, что обычное указание типа не защищает от изменений.

Использование as const

ts
const Apple = { nme: "Apple" } as const; // объект неизменяемый
function isFruit(payload: unknown): payload is Fruit { return payload && "name" in payload; } // простейший type-guard для наглядности
isFruit(Apple); // ⚠️ false

В названии поля допущена ошибка (nme вместо name), поэтому проверка не сработала. Это показывает, что as const фиксирует значения, но не гарантирует соответствие интерфейсу — легко допустить опечатку или пропустить обязательное поле.

Комбинация as const и интерфейса

Кажется логичным совместить as const с интерфейсом:

ts
const Apple: Fruit = { name: "Apple" } as const;
Apple.name = "Orange"; // ⚠️ нет ошибки

На практике as const "теряется": тип сужается до { name: string }, и свойство снова становится изменяемым.

Readonly

ts
const Apple: Readonly<Fruit> = { name: "Apple" };
Apple.name = "Orange"; // ✅ ошибка компиляции

Такой способ защитит от изменения свойств, а переданный тип Fruit не даст ошибиться в названиях полей. Но есть нюанс: IDE покажет тип Apple как { name: string }, а не конкретное значение { name: "Apple" }. Будет потерян "конкретный тип", ради которого обычно и используется as const.

ReadonlyDeep

Если вам достаточно Readonly, не забывайте, что он работает только на верхнем уровне объекта: вложенные объекты и массивы останутся изменяемыми. В таких случаях стоит использовать утилиту ReadonlyDeep из type-fest. Она рекурсивно обходит все уровни и делает все свойства неизменяемыми.

Решение

Чтобы одновременно зафиксировать значения и проверить правильность структуры, используется оператор satisfies. Он проверяет, что выражение совместимо с указанным типом, при этом сохраняя исходный (более конкретный) тип для вывода. Документация: satisfies operator

ts
const Apple = { name: "Apple" } as const satisfies Fruit;
Apple.name = "Orange"; // ✅ ошибка компиляции

При использовании конструкции as const satisfies %type% TypeScript корректно проверяет правильность полей в объекте, as const сохраняет неизменность значений, а попытка присвоить новое значение свойству ловится на этапе компиляции.

Итог

as const satisfies — простой способ объединить две цели: сохранить значения объекта неизменными и убедиться, что его структура соответствует интерфейсу. Особенно полезно для крупных объектов, например конфигураций, где легко допустить опечатку или ошибку в типах.