Перейти к содержимому
Теория · Массивы и кортежи · урок 6 из 7

as const

Помните запечатанную банку const? На её наклейке TypeScript пишет точное содержимое: const limit = 5 получает литеральный тип 5. С массивом так не выходит: даже в const его элементы получают общий тип.

Посмотрим, где это мешает. Функция принимает только размеры из фиксированного списка, а размеры лежат в массиве:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log(size);
}

const sizes = ["S", "M", "L"];
order(sizes[0]); // ✗
TEXT
error TS2345: Argument of type 'string' is not assignable to parameter of type 'Size'.

У sizes тип string[]. Массив в const можно менять (запись sizes[0] = "XXL" разрешена), поэтому TypeScript расширяет "S", "M" и "L" до string, как у let. А string к параметру Size не подходит.

Как работает as const

Запись as const после литерала говорит TypeScript: это значение не изменится, опиши его точно. Добавим её к массиву:

TypeScript
type Size = "S" | "M" | "L";

function order(size: Size) {
    console.log(size);
}

const sizes = ["S", "M", "L"] as const;
order(sizes[0]); // S

Теперь у sizes тип readonly ["S", "M", "L"], и вызов компилируется. В этом типе сразу три изменения:

  • литеральные типы сохранились: на месте 0 стоит "S", а не string;
  • массив стал кортежем: в нём ровно три места, и у каждого свой тип;
  • всё стало только для чтения: ни sizes.push("XL"), ни sizes[0] = "M" tsc не пропустит.

На push компилятор отвечает так же, как у любого массива только для чтения:

TEXT
error TS2339: Property 'push' does not exist on type 'readonly ["S", "M", "L"]'.

Это как ряд шкафчиков, запаянный на заводе: на каждой дверце написано точное содержимое, и ни одну не открыть.

as const у объекта

as const работает и с объектным литералом:

TypeScript
const config = { mode: "dark", retries: 3 } as const;
console.log(config.mode, config.retries); // dark 3

Без as const у config был бы тип { mode: string; retries: number; }. С ним — { readonly mode: "dark"; readonly retries: 3; }:

  • каждое свойство стало readonly, и config.retries = 4 даст ошибку TS2540;
  • у каждого свойства литеральный тип: "dark" и 3.

as const действует на всю глубину. У { sizes: ["S", "M"] } as const тип { readonly sizes: readonly ["S", "M"]; }: только для чтения и свойство, и массив внутри него. Модификатор readonly у свойства так не умеет: он защищает один уровень.

Здесь аналогия с запаянными шкафчиками перестаёт работать. Запаяно только для tsc: as const стирается, и в JavaScript-файле остаётся const sizes = ["S", "M", "L"]; — обычный массив, который при запуске можно изменить.

Когда пригодится as const

as const нужен там, где набор значений известен заранее и не меняется:

  • список вариантов для выбора: размеры, роли, пункты меню;
  • настройки, которые программа только читает;
  • значения, которые потом передают в параметры литеральных типов, как в order(sizes[0]).

Есть и другой путь — аннотация. С const sizes: Size[] = ["S", "M", "L"]; вызов order(sizes[0]) тоже скомпилируется. Но это обычный массив: его можно менять, и длина у него любая. as const закрепляет и значения, и длину.

Массив as const — только для чтения, поэтому в параметр типа string[] его не передать. У такого параметра пишут readonly string[].

На собеседовании любят спрашивать, чем const отличается от as const. const запрещает записать в имя новое значение. as const описывает само значение: сохраняет его литералы и запрещает менять его содержимое.

Частая ошибка

as const дописали к переменной, а не к литералу:

TypeScript
const sizes = ["S", "M", "L"];
const fixed = sizes as const; // ✗
TEXT
error TS1355: A 'const' assertion can only be applied to references to enum members, or string, number, boolean, array, or object literals.

tsc отвечает, что as const применяют только к литералам — строковым, числовым, логическим, к литералам массива и объекта — и к членам enum. Ко второй строке тип sizes уже выведен как string[], и литералы "S", "M", "L" из него не вернуть. Пишите as const сразу после литерала: const sizes = ["S", "M", "L"] as const;.

Главное

  • as const после литерала сохраняет литеральные типы и делает значение только для чтения.
  • Массив с as const становится кортежем только для чтения: ["S", "M"] as const — это readonly ["S", "M"].
  • У объекта с as const все свойства readonly и литеральных типов, на всю глубину.
  • as const пишут у самого литерала, и проверяет его только tsc.