as const и const assertions

as const (const assertion) — постфиксный оператор TypeScript, который говорит компилятору: «трактуй это выражение как максимально узкий литеральный тип и сделай все свойства readonly», превращая "hello" из string в "hello" и объект из мутабельного в readonly.

Зачем нужно

По умолчанию TypeScript расширяет типы: let x = "north" даёт string, а не "north". as const фиксирует литеральный тип, что критично для discriminated unions, enum-замен и корректной работы typeof arr[number] для извлечения union типов.

Где используется

  • Замена enumas const объект + typeof union
  • Фиксация конфигурационных объектов как неизменяемых
  • Кортежи с точными типами вместо (string | number)
  • Константы маршрутов, событий, ключей конфигурации

Основной контент

Примитивы

let x = "north";        // x: string (widened)
const y = "north";      // y: "north" (literal — const сужает)
let z = "north" as const; // z: "north" (literal — явно сужено)

Объекты

// Без as const — типы полей широкие
const config = {
  host: "localhost",
  port: 3000,
  debug: true,
};
// { host: string; port: number; debug: boolean }

// С as const — типы полей — литералы, поля readonly
const config = {
  host: "localhost",
  port: 3000,
  debug: true,
} as const;
// { readonly host: "localhost"; readonly port: 3000; readonly debug: true }

// config.host = "example.com"; // Error — readonly

Массивы и кортежи

// Без as const — тип: string
const directions = ["north", "south", "east", "west"];
// directions: string

// С as const — тип: readonly ["north", "south", "east", "west"]
const directions = ["north", "south", "east", "west"] as const;
// directions: readonly ["north", "south", "east", "west"]

// Извлечение union типов
type Direction = typeof directions[number];
// "north" | "south" | "east" | "west"

Замена enum через as const

const Status = {
  Pending:  "PENDING",
  Active:   "ACTIVE",
  Inactive: "INACTIVE",
} as const;

type Status = typeof Status[keyof typeof Status];
// "PENDING" | "ACTIVE" | "INACTIVE"

function setStatus(s: Status): void { /* ... */ }

setStatus(Status.Active);  // OK
setStatus("ACTIVE");       // OK — строка совместима с "ACTIVE"
setStatus("unknown");      // Error

as const в функциях

// Функция возвращает кортеж, но TypeScript по умолчанию даёт массив
function getCoords() {
  return [55.75, 37.62]; // number — не [number, number]!
}

// Исправление:
function getCoords() {
  return [55.75, 37.62] as const; // readonly [55.75, 37.62]
}

// Или явная аннотация:
function getCoords: [number, number] {
  return [55.75, 37.62];
}

Объединение as const с другими типами

// Частичное применение — только нужные поля
const partialConfig = {
  host: "localhost" as const, // только host — литерал
  port: 3000,                 // port остаётся number
};
// { host: "localhost"; port: number }

Частые ошибки

  • as const на let переменной — можно, но let всё равно позволяет переприсвоение переменной; сама переменная не readonly.
  • Ожидать глубокое замораживание в рантаймеas const только на уровне типов; для рантайм-защиты нужен Object.freeze.
  • typeof obj[keyof typeof obj] без as const — без as const даст string, а не union литералов.
  • Применять к динамическим значениямas const требует что значение было литералом на момент компиляции; переменная с as const не работает как as const.

Связанные темы

Ресурсы