as const каже TypeScript вивести найвужчий тип і зробити все лише для читання:
const roles = ['admin', 'editor', 'viewer']; // string[]
const roles2 = ['admin', 'editor', 'viewer'] as const; // readonly ['admin', 'editor', 'viewer']
type Role = (typeof roles2)[number]; // 'admin' | 'editor' | 'viewer'
roles2.push('guest'); // помилка: масив лише для читання
Головний прийом - одне джерело правди для значень і типу. Список значень потрібен і під час виконання (випадний список, валідація), і як тип. З as const тип виводиться зі значень, і вони не розходяться:
const ORDER_STATUSES = ['new', 'paid', 'shipped'] as const;
type OrderStatus = (typeof ORDER_STATUSES)[number];
function isOrderStatus(value: string): value is OrderStatus {
return (ORDER_STATUSES as readonly string[]).includes(value);
}
Це часто краща альтернатива enum: звичайний JavaScript-масив без особливого синтаксису.
Об'єкти:
const config = { api: { timeout: 5000 } } as const;
config.api.timeout = 10; // помилка - readonly на всіх рівнях
readonly у типах - для параметрів і полів, які функція не повинна змінювати:
function total(items: readonly CartItem[]): number {
items.sort(); // помилка: sort змінює масив
return items.reduce((sum, i) => sum + i.price, 0);
}
interface User {
readonly id: number;
name: string;
}
ReadonlyArray<T> / readonly T[] прибирають з типу методи, що змінюють масив (push, sort, splice).
Що варто знати:
- це лише перевірка компілятора. Під час виконання об'єкт звичайний - змінити його можна через
as anyчи з JavaScript-коду. Для справжньої незмінності -Object.freeze; readonlyповерхневий у звичайних типах:readonly items: Item[]забороняє замінити масив, але не змінити його вміст. Для вкладених структур -readonlyна кожному рівні чи утиліта на кшталтDeepReadonly;- масив
readonly string[]не можна передати туди, де очікуютьstring[]- функції, що не змінюють масив, варто оголошувати зreadonlyв параметрі.