Menu

Литеральные типы и as const в TypeScript: примеры

Литеральный тип это тип ровно с одним значением, например "GET" или 404. Строковые, числовые и логические литеральные типы, объединения литералов, почему let расширяет тип, а const нет, что делает as const и параметры типа const.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Литеральный тип это тип ровно с одним значением: "up" это тип, единственный элемент которого строка "up", а 404 это тип, единственный элемент которого число 404. Сами по себе они не очень полезны. Объединённые в union, они дают переменную, которая принимает фиксированный набор значений и ничего больше.

Без комментария @ts-expect-error последний вызов был бы ошибкой компиляции (TS2345). С ним программа компилируется, а вызов всё равно выполняется и печатает moving north by 1: литеральные типы существуют только для компилятора, а во время выполнения значение это обычная строка.

Строковые, числовые и логические литералы

Любое строковое, числовое, bigint или логическое значение можно записать как тип. Тогда компилятор принимает только это конкретное значение.

let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;

type Port = 80 | 443 | 8080;
const port: Port = 443;

Сам boolean это просто объединение true | false, поэтому после сужения boolean через if (flag) в ветке else остаётся false.

Литеральный типДопускаетБолее широкий тип
"GET"только строку "GET"string
404только число 404number
10nтолько bigint 10bigint
trueтолько trueboolean

Объединения литералов

Обычно используют объединение, которое перечисляет все допустимые значения. Внутри функции компилятор сужает объединение по мере проверок, поэтому каждая ветка точно знает, какое у неё значение.

Объединение строковых литералов это обычная в TypeScript альтернатива enum. Оно ничего не стоит во время выполнения, значения это обычные строки, которые можно выводить в лог и передавать в JSON, а опечатка становится ошибкой компиляции. Подробнее о компромиссах в разделе enum.

Расширение типа: let и const

Когда TypeScript выводит тип из литерала, он смотрит, может ли значение измениться. Переменную const нельзя переприсвоить, поэтому она сохраняет литеральный тип. Переменную let можно, поэтому её тип расширяется до общего.

Наведите курсор на каждое имя в редакторе, чтобы увидеть выведенный тип. Если нужна переменная let, которая хранит только определённые значения, аннотируйте её: let mode: "light" | "dark" = "light".

Почему свойства объекта расширяются

Свойства объектного литерала изменяемые, поэтому они тоже расширяются, даже если объект хранится в const. Чаще всего именно так на литеральные типы натыкаются случайно:

Компилятор сообщает:

index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

req выводится как { url: string; method: string }, потому что код ниже мог бы выполнить req.method = "DELETE". Есть три способа исправить:

Четвёртый вариант это satisfies, который проверяет объект на соответствие типу, сохраняя литеральные типы его свойств.

as const

as const это утверждение const. Поставьте его после выражения, и компилятор выведет самый узкий возможный тип:

  • строковые, числовые и логические значения сохраняют литеральные типы
  • свойства объектов становятся readonly
  • литералы массивов становятся кортежами readonly фиксированной длины

Утверждение действует только при компиляции. Сгенерированный JavaScript это тот же объектный литерал без as const, так что ничто не мешает другому коду изменить его во время выполнения. Если нужна гарантия во время выполнения, вызовите ещё и Object.freeze.

Тип объединения из массива с as const

Частый приём: хранить допустимые значения в одном массиве, по которому можно пройти циклом во время выполнения, и выводить из него тип объединения. (typeof arr)[number] означает «тип любого элемента arr».

Без as const ROLES имел бы тип string[], а Role был бы просто string. Приведение к readonly string[] в isRole нужно потому, что includes у кортежа литералов принимает только эти литералы, а смысл функции в том, чтобы проверить строку, которая может им не быть. Тот же приём с объектом работает для отображений ключ/значение: const Status = { Active: "active", Banned: "banned" } as const, затем type Status = (typeof Status)[keyof typeof Status].

Параметры типа const

Обобщённая функция обычно расширяет переданные ей литералы. Начиная с TypeScript 5.0 параметр типа можно пометить как const, и тогда компилятор выводит аргумент так, будто к нему применено as const, не требуя от вызывающего кода писать это.

В основном это инструмент для авторов библиотек: определения маршрутов, билдеры и вспомогательные функции для схем используют его, чтобы вызывающий код получал точные типы из обычных литералов.

Значения const

Ключевое слово const встречается в коде на TypeScript в четырёх разных местах:

СинтаксисВидЧто делает
const x = 1объявление JavaScriptпривязку нельзя переприсвоить; литеральное значение сохраняет литеральный тип
expr as constутверждение TypeScriptсамый узкий тип: литералы, свойства readonly, readonly-кортежи
function f<const T>()параметр типа TypeScriptвыводит аргументы так, будто к ним применено as const
const enum E {}enum в TypeScriptenum, члены которого подставляются при компиляции

Ни один из них не замораживает объект во время выполнения. const obj = { a: 1 } по-прежнему допускает obj.a = 2; ошибкой будет только переприсваивание самого obj.

Часто задаваемые вопросы

Что такое литеральный тип в TypeScript?

Тип, который допускает ровно одно значение. "GET" это тип, единственное значение которого строка "GET", 404 это тип с единственным значением 404, а true это тип с единственным значением true. Полезнее всего они в объединениях вроде type Method = "GET" | "POST".

Что делает as const в TypeScript?

as const это утверждение const (const assertion). Оно говорит компилятору вывести для выражения самый узкий тип: строковые и числовые значения сохраняют литеральные типы, свойства объектов становятся readonly, а литералы массивов становятся readonly-кортежами. Меняется только тип; во время выполнения значение остаётся тем же обычным объектом или массивом и не замораживается.

Почему TypeScript выводит string вместо моего литерала?

Потому что значение изменяемое. let x = "a" и свойство в { method: "GET" } можно позже переприсвоить, поэтому TypeScript расширяет их до string. Переменная const сохраняет литеральный тип "a". Чтобы сохранить литералы внутри объекта, аннотируйте его литеральным типом, используйте as const или satisfies.

Чем const отличается от as const?

const это объявление JavaScript: переменную нельзя переприсвоить, но объект, на который она указывает, по-прежнему можно менять. as const это утверждение типа TypeScript: в системе типов оно делает каждое свойство значения readonly и литеральным. Ни то ни другое не замораживает объект во время выполнения; для этого есть Object.freeze.

Как получить тип объединения из массива строк?

Объявите массив с as const, затем проиндексируйте его тип через number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; даёт "admin" | "user". Без as const массив имеет тип string[], и результат будет просто string.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ