Привет, Хабр!
В разработке часто возникают ситуации, когда точность типов и нежелание допускать неясности в коде становятся первостепенными задачами. В таких случаях, разработчикам приходится искать инструменты, предоставляющие максимальную ясность и строгость в определении данных. Один из таких инструментов — ключевое слово as const
. В данной статье мы рассмотрим, как as const
может повысить уровень строгости и предсказуемости, а также рассмотрим практические примеры его использования для создания неизменяемых и точных типов.
Подробнее о “as const”
Когда вы используете as const
для переменной или значения, TypeScript уточняет тип этой переменной до ее точного значения или комбинации литеральных типов. Это часто используется для создания неизменяемых значений и гарантирования того, что TypeScript будет рассматривать значения как конкретные литералы, а не расширять типы.
Пример:
Представьте, что нам нужно написать функцию для открытия входной двери в дом. Пишем функцию, которая принимает параметром ключ, и проводит с ним какие-либо операции.
const wallet = {
key: "open_door_pls"
}
const openDoor = (key: "open_door_pls") => {
//...
}
Но, когда мы пытаемся использовать функцию openDoor
с ключом из нашего бумажника, почему-то происходит следующее:
const wallet = {
key: "open_door_pls"
}
const openDoor = (key: "open_door_pls") => {
//...
}
openDoor(wallet.key) // ERROR: Argument of type 'string' is not assignable to parameter of type '"open_door_pls"'
Почему же мы попали в такую ситуацию?
Всё дело в том, что wallet.key
у нас никак не привязан к значению "open_door_pls”
, и по сути является просто элементом с типом string
, значение которого можно легко изменить на любое другое:
const wallet = {
key: "open_door_pls"
}
wallet.key = "cucumber" // код отрабатывает без ошибок
Чтобы избежать такого поведения, и сделать все элементы wallet полноценными, неизменяемыми (readonly
) значениями, мы можем воспользоваться конструкцией as const
:
const wallet = {
key: "open_door_pls"
} as const
wallet.key = "cucumber" // ERROR: Cannot assign to 'key' because it is a read-only property.
Элементы wallet
привязались к своим значениям и теперь имеют флаг readonly
.
Что в конечном итоге решило нашу проблему с параметром функции openDoor
:
const wallet = {
key: "open_door_pls"
} as const;
const openDoor = (key: "open_door_pls") => {
//...
};
openDoor(wallet.key) // код отрабатывает без ошибок
Почему не Object.freeze()?
Проблема Object.freeze()
заключается в том, что после заморозки объекта, readonly
присваевается только элементам на верхнем уровне вложенности.
const car = Object.freeze({
name: "Porshe Cayenne",
equipment:{
engine: "MDC.AB"
}
});
car.equipment.engine = "F8CV"; // без проблем изменили двигатель
При использовании as const
компилятор на любом уровне вложенности укажет, что программист пытается изменить константу.
const car = {
name: "Porshe Cayenne",
equipment:{
engine: "MDC.AB"
}
} as const;
car.equipment.engine = "F8CV"; // ERROR: Cannot assign to 'engine' because it is a read-only property.
Замена enum-ам?
as const
превосходно подходит в качестве альтернативы enum
.
О минусах enum
можно подробно почитать в этой статье.
Пример перехода кода с enum
на as const
:
Код, с использованием enum
:
enum Wallet {
KEY = "open_door_pls"
};
const openDoor = (key: "open_door_pls") => {
//...
};
openDoor(Wallet.KEY)
Код, переписанный на as const
:
const wallet = {
key: "open_door_pls"
} as const;
const openDoor = (key: "open_door_pls") =>{
//...
};
openDoor(wallet.key);
Ещё одна действительно крутая особенность as const
заключается в том, что использование as const
позволяет быть очень гибким в обращении с ключевым объектом:
const friendsDict = {
Alfred: "101 Pine Road, Toronto, ON M5A 1A1, Canada",
Liam: "777 Sycamore Lane, Tokyo, 100-0001, Japan",
Mia: "666 Willow Street, Paris, 75001, France",
} as const;
type FriendName = keyof typeof friendsDict; // "Alfred" | "Liam" | "Mia"
type FriendAddress = (typeof friendsDict)[keyof typeof friendsDict];
//"101 Pine Road, Toronto, ON M5A 1A1, Canada" | "777 Sycamore Lane, Tokyo, 100-0001, Japan" | "666 Willow Street, Paris, 75001, France"
Резюмируя
Мы рассмотрели конструкцию as const
в TypeScript и её роль в создании более строгих и предсказуемых типов данных. Надеюсь, что данная статья помогла вам лучше понять, как использование as const
может повысить уровень безопасности и ясности вашего кода.
Таким образом, внедрение as const
в ваш код может быть ключом к облегчению его поддержки в будущем и уменьшению вероятности ошибок. Пользуйтесь этим инструментом с умом, и ваш код станет более чистым, надежным и легко поддерживаемым!
До новых встреч в мире строгой типизации!
Ещё полезностей по теме
Комментарии (15)
SWATOPLUS
06.01.2024 22:45+4Употребляйте термин строгая типизация корректно. В статье идёт речь про точные типы или литеральные типы. Строгая типизация это отсутствие неявного преобразования типов и отсутствие каламбура типов.
Статье для полноты не хватает реализации метода deepFreeze с поддержкой точного типа.
as const
уточняет тип и выдает ошибки модификации во время транспиляции. Но если передать этот объект js-коду или сделать as any, то можно будет модифицировать этот объект. Поэтому важно не только делать типas const
но и защищать объект от посягательств кода третьих лиц.
Evgeniiit
06.01.2024 22:45Отличные замечания. А а какой момент может произойти модификация объекта? (В сорце? В рантайме? При транспиляции?)
SolovevSerg
06.01.2024 22:45+1Кстати, чтобы написать такую функцию
deepFreeze
, весьма полезен новый модификаторconst
для праметров типов в дженериках из TypeSscript 5. Вот простейший пример реализации с ним:/* ???? */ function deepFreeze<const T extends object>(obj: T): T { Object.freeze(obj); Object.values(obj) .filter(nested => nested !== null && typeof nested === 'object') .forEach(deepFreeze); return obj; } // const frozen: readonly ["a", {readonly b: 1;}] const frozen = deepFreeze(["a", {b: 1}]);
Без него всякий раз пришлось бы лишний раз писать
as const
const frozen = deepFreeze(["a", {b: 1}] as const);
Подробнее про модификатор
const
в документации TS.
stryaponoff
06.01.2024 22:45+2По поводу использования const assertion в качестве «замены enum»: рекомендую посмотреть в сторону конструкции `const enum` — она объединяет лучшее из обоих миров.
ArchieSup Автор
06.01.2024 22:45+1Хороший вариант. Главное помнить о подводных камнях:
https://www.typescriptlang.org/docs/handbook/enums.html#const-enum-pitfalls
cijic
06.01.2024 22:45-2Начались костыли как в JS.
Вам не кажется что писать const ... as const говорит об изначальном косяке в TS, который надо было исправить в любой версии, а не добавлять тавтологию?
Format-X22
06.01.2024 22:45Дело в том что const делает неизменной переменной, только вот объекты по ссылке передаются. Защита от изменения у ссылки остается, а вот у внутренностей нет. И хороший вопрос что лучше - защищать целиком или всё же только верхний уровень, удобство кардинально разное. В JS выбрали только ссылки, TS поддержали. Возможно имело бы смысл сделать final в дополнении к const и let, для полной заморозки, но пока дополнительно дописывается дабы не вводить новое ключевое слово.
Format-X22
06.01.2024 22:45+1Object.freeze работает на уровне JS и замораживает объект по настоящему, а вот as const лишь на уровне TS и не защищает от изменений вне компилятора TS. Механизмы очень разные, стоит помнить об этом.
modelair
06.01.2024 22:45любая возможность TS работает только в рамках компилятора, а не только as const. это ж всего-лишь декорации
Format-X22
06.01.2024 22:45Об этом и речь, чтобы никто не строил ожиданий и понимал разницу, если новичок прочтет.
StiPAFk
06.01.2024 22:45все аргументы из статьи почему enum плох разваливаются об ТС5 (намберы пофиксили) или о понимание почему важно использовать Branding для перечисления.
const enum лучшее решение.
edtech
Не совсем ясна суть примера, но, теоретически, решение с заданием статического типа для переменной ключа, может быть лучше масштабируемым:
meonsou
Тогда лучше заменить
as OpenDoorKey
наas const satisfies OpenDoorKey
чтобы нельзя было рандомную строку туда вписать