Типизация as const
В TypeScript as const
позволяет фиксировать значения литералов и делать их максимально конкретными: строки не расширяются до string
, числа — до number
, а свойства объектов и массивы становятся readonly
. Это удобно, когда нужно работать с неизменяемыми объектами или использовать их как "enum-подобные" конструкции. Документация: const
assertions
Посмотрим, как это работает на практике и какие подводные камни возникают при использовании обычной типизации и as const
.
Примеры
Прямое указание типа
type Fruit = { name: string };
const Apple: Fruit = { name: "Apple" }; // создаём объект Apple и указываем, что он имеет тип Fruit
Apple.name = "Orange"; // ⚠️ нет ошибки
Свойство остаётся изменяемым. Видно, что обычное указание типа не защищает от изменений.
Использование as const
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
с интерфейсом:
const Apple: Fruit = { name: "Apple" } as const;
Apple.name = "Orange"; // ⚠️ нет ошибки
На практике as const
"теряется": тип сужается до { name: string }
, и свойство снова становится изменяемым.
Readonly
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
const Apple = { name: "Apple" } as const satisfies Fruit;
Apple.name = "Orange"; // ✅ ошибка компиляции
При использовании конструкции as const satisfies %type%
TypeScript корректно проверяет правильность полей в объекте, as const
сохраняет неизменность значений, а попытка присвоить новое значение свойству ловится на этапе компиляции.
Итог
as const satisfies
— простой способ объединить две цели: сохранить значения объекта неизменными и убедиться, что его структура соответствует интерфейсу. Особенно полезно для крупных объектов, например конфигураций, где легко допустить опечатку или ошибку в типах.