Что означает "as const" в TypeScript и для чего это нужно?
Что такое as const и как он работает?
as const — это специальная конструкция в TypeScript, которая называется const assertion (утверждение константы). Она заставляет TypeScript выводить максимально узкие (конкретные) типы, делая все свойства объекта доступными только для чтения.
Давайте разберёмся на примерах:
1. Литеральные типы вместо общих
Без as const:
const color = 'red';
// typeof color -> string (общий тип)
С as const:
const color = 'red' as const;
// typeof color -> "red" (конкретный литеральный тип)
Почему так? Обычно TypeScript расширяет типы: если вы присваиваете строку переменной с const, он понимает, что значение не изменится, но тип всё равно выводится как string. as const говорит: "Нет, это именно строка 'red', а не любая строка".
2. Массивы становятся кортежами
// Без as const
const arr1 = ['red', 'green', 'blue'];
// typeof arr1 -> string[] (может быть любой длины)
// С as const
const arr2 = ['red', 'green', 'blue'] as const;
// typeof arr2 -> readonly ["red", "green", "blue"] (фиксированный кортеж)
Это полезно, когда вам нужно гарантировать, что массив содержит именно эти элементы в этом порядке.
3. Объекты становятся глубоко неизменяемыми
const config = {
api: 'https://api.example.com',
port: 3000
} as const;
// Все поля readonly, типы — литералы:
// config.api -> readonly "https://api.example.com"
// config.port -> readonly 3000
Почему as const, если есть const?
Это частая путаница. const в JavaScript/TypeScript — это привязка переменной. Она означает, что вы не можете переназначить переменную:
const x = 5;
x = 10; // Ошибка! Нельзя переназначить const
Но содержимое объекта или массива можно изменить:
const obj = { a: 1 };
obj.a = 2; // Это работает!
obj.b = 3; // И это тоже
as const же делает глубокую неизменяемость на уровне типов (читай: readonly). Он не влияет на выполнение кода, только на проверку типов.
Практические сценарии использования
- Redux Actions: гарантия, что тип action — это именно литерал, а не строка. Это помогает в discriminated unions (различающихся объединениях).
- Enum-like объекты: замена enum с более гибкой типизацией.
- Конфигурации: точные типы для константных конфигов.
- Type narrowing (сужение типов): помогает TypeScript точно определить, с каким значением вы работаете.
Пример: Redux Action
const ActionTypes = {
ADD_TODO: 'ADD_TODO',
REMOVE_TODO: 'REMOVE_TODO',
TOGGLE_TODO: 'TOGGLE_TODO'
} as const;
type ActionType = typeof ActionTypes[keyof typeof ActionTypes];
// ActionType -> "ADD_TODO" | "REMOVE_TODO" | "TOGGLE_TODO"
// Теперь action creator получает точный тип:
function createAction(type: ActionType, payload: any) {
return { type, payload } as const;
}
Без as const тип ActionType был бы просто string, что свело бы на нет преимущества TypeScript.
Важное замечание
as const — это только проверка типов. На скомпилированный JavaScript он не влияет. Это инструмент для разработчика, чтобы TypeScript мог точнее проверять ваш код.
Альтернативный взгляд: as const как инструмент для discriminated unions
Позвольте дополнить коллегу с другой стороны — как as const помогает в работе с discriminated unions (различающимися объединениями).
Представьте, что у вас есть функция, которая обрабатывает разные типы событий:
type Event =
| { type: 'click'; x: number; y: number }
| { type: 'keypress'; key: string }
| { type: 'focus'; element: HTMLElement };
function handleEvent(event: Event) {
switch (event.type) {
case 'click':
// TypeScript знает: здесь event.x и event.y доступны
console.log(event.x, event.y);
break;
case 'keypress':
// TypeScript знает: здесь event.key доступен
console.log(event.key);
break;
case 'focus':
// TypeScript знает: здесь event.element доступен
console.log(event.element);
break;
}
}
Если бы мы не использовали as const при создании таких объектов, TypeScript не смог бы сузить тип в switch:
const badEvent = { type: 'click', x: 10, y: 20 };
// typeof badEvent.type -> string (не "click")
С as const всё работает идеально:
const goodEvent = { type: 'click', x: 10, y: 20 } as const;
// typeof goodEvent.type -> "click"
Это особенно важно при работе с библиотеками вроде Redux Toolkit, где каждый action creator возвращает объект с type, и для корректной работы discriminated unions нужно, чтобы тип был точным литералом.
Итог: если вы видите, что TypeScript не может определить, с каким именно вариантом объединения вы работаете — попробуйте добавить as const к объекту, который определяет тип.