Что означает "as const" в TypeScript и когда его использовать?
Что такое as const и как он работает?
as const — это специальная конструкция в TypeScript, которая называется const assertion (константное утверждение). Она говорит компилятору: "Считай, что это значение никогда не изменится, и выведи максимально точный тип". В отличие от обычного as, который просто меняет тип, as const делает тип более строгим и неизменяемым.
Основные эффекты as const
- Литеральные типы — значения становятся литеральными (например,
'hello'вместоstring) - Неизменяемость (readonly) — массивы и объекты помечаются как
readonly - Глубокий вывод — эффект применяется рекурсивно ко всем вложенным элементам
Примеры с пояснениями
1. Массивы
// Обычный массив
const fruits = ['apple', 'banana'];
// Тип: string[]
// Можно изменять: fruits.push('orange') — работает
// С as const
const fruitsConst = ['apple', 'banana'] as const;
// Тип: readonly ["apple", "banana"]
// Нельзя изменять: fruitsConst.push('orange') — ошибка!
// Каждый элемент имеет точный литеральный тип
2. Объекты
// Обычный объект
const config = {
mode: 'development',
port: 3000
};
// Тип: { mode: string; port: number }
// Можно менять: config.mode = 'production' — работает
// С as const
const configConst = {
mode: 'development' as const,
port: 3000
};
// Тип: { mode: "development"; port: 3000 }
// Нельзя менять: configConst.mode = 'production' — ошибка!
// port: number (так как 3000 — число, а не литерал)
Реальные сценарии использования
Сценарий 1: Enum-подобные объекты
const Status = {
SUCCESS: 'success',
ERROR: 'error',
PENDING: 'pending'
} as const;
type StatusType = typeof Status[keyof typeof Status];
// StatusType = "success" | "error" | "pending"
function handleStatus(status: StatusType) {
// Работает только с указанными значениями
}
handleStatus('success'); // OK
handleStatus('invalid'); // Ошибка!
Сценарий 2: Строгая типизация конфигураций
const API_ROUTES = {
users: '/api/users',
posts: '/api/posts',
comments: '/api/comments'
} as const;
function fetchData(route: typeof API_ROUTES[keyof typeof API_ROUTES]) {
return fetch(route);
}
fetchData('/api/users'); // OK
fetchData('/api/unknown'); // Ошибка!
Сценарий 3: Кортежи с фиксированной длиной
// Без as const — это просто массив
const pair = ['x', 1]; // тип: (string | number)[]
// С as const — это кортеж
const tuple = ['x', 1] as const; // тип: readonly ["x", 1]
function processPair(pair: readonly [string, number]) {
const [str, num] = pair;
// str: string, num: number
}
processPair(tuple); // OK
processPair(pair); // Ошибка! pair может быть любой длины
Когда НЕ стоит использовать as const
- Если вам нужно изменять массив или объект (добавлять/удалять элементы)
- Если вы работаете с динамическими данными, которые могут меняться
- В случаях, когда широкая типизация (string, number) предпочтительнее
as const — мощный инструмент для создания безопасных и точных типов. Используйте его, когда хотите гарантировать, что значение останется неизменным, и TypeScript будет строго следить за этим на этапе компиляции.
Альтернативный взгляд: as const как способ создания константных контекстов
Вместо того чтобы думать о as const как о приведении типа, представьте его как способ создания "константного контекста" для значения. Это особенно полезно при работе с условными типами и маппингом.
// Пример с функциями
function createAction(type: string, payload?: any) {
return { type, payload } as const;
}
const action = createAction('LOGIN', { user: 'Alice' });
// Тип: { readonly type: string; readonly payload: { user: string } }
// Но лучше так:
function createAction2<T extends string, P>(type: T, payload: P) {
return { type, payload } as const;
}
const action2 = createAction2('LOGIN', { user: 'Alice' });
// Тип: { readonly type: "LOGIN"; readonly payload: { user: string } }
Практический совет: Используйте as const для создания дискриминированных юнионов (discriminated unions). Это основа для TypeScript-редьюсеров в Redux или для работы с состоянием в React.
type Action =
| { type: 'ADD_TODO'; text: string }
| { type: 'REMOVE_TODO'; id: number }
| { type: 'TOGGLE_TODO'; id: number };
// Без as const вам пришлось бы писать type: 'ADD_TODO' as const
// в каждом объекте, что неудобно
Если вам нужна изменяемость, но при этом точные литеральные типы, рассмотрите использование Object.freeze() на уровне выполнения, а as const оставьте для типов. Но помните: as const работает только на этапе компиляции и не влияет на рантайм.