Как объявить массив фиксированной длины в TypeScript?
Корректное объявление массивов фиксированной длины в TypeScript
Вы на правильном пути — кортежи (tuple) это именно то, что нужно. Однако есть нюансы, которые важно понимать. Давайте разберём всё по порядку.
1. Базовый синтаксис: кортежи
Кортеж — это массив с фиксированным количеством элементов, где каждый элемент может иметь свой тип. Объявляется он так:
let point: [number, number] = [10, 20]; // ровно 2 числа
let rgb: [number, number, number] = [255, 0, 0]; // ровно 3 числа
let person: [string, number] = ["Alice", 30]; // строка и число
Важно: кортежи не защищают от методов push, pop, splice и т.д. Это известное ограничение TypeScript. Компилятор не проверяет длину массива во время выполнения — только типы элементов.
2. Как запретить изменение длины кортежа
Используйте модификатор readonly:
let point: readonly [number, number] = [10, 20];
point.push(30); // Ошибка: Свойство 'push' не существует в типе 'readonly [number, number]'
point[0] = 5; // Ошибка: Индексная сигнатура в типе 'readonly [number, number]' допускает только чтение
Теперь массив полностью защищён от изменений — ни длину, ни значения элементов изменить нельзя. Это идеальный вариант для фиксированной длины.
3. Как создать кортеж на N элементов (без ручного перечисления)
Если нужно 100 элементов одного типа, писать 100 раз number неудобно. Используйте вспомогательный тип:
// Создаём тип-кортеж заданной длины
type Tuple<T, N extends number> = N extends N
? number extends N ? T[] : _TupleOf<T, N, []>
: never;
type _TupleOf<T, N extends number, R extends unknown[]> =
R['length'] extends N ? R : _TupleOf<T, N, [T, ...R]>;
// Пример использования
type FixedArray100 = Tuple<number, 100>;
let arr: FixedArray100 = new Array(100).fill(0); // ок
let arr2: FixedArray100 = [1, 2]; // Ошибка: Source has 2 element(s) but target requires 100
Это рекурсивный тип, который генерирует кортеж нужной длины. Работает для небольших N (до ~50-100). Для больших значений лучше использовать обычные массивы с проверкой во время выполнения.
4. Альтернатива: проверка длины в рантайме
Если вам критично контролировать длину именно во время выполнения (например, для 1000+ элементов), используйте класс-обёртку:
class FixedArray<T> {
private data: T[];
constructor(private length: number, ...items: T[]) {
if (items.length !== length) {
throw new Error(`Expected ${length} elements, got ${items.length}`);
}
this.data = items;
}
get(index: number): T {
if (index < 0 || index >= this.length) throw new Error("Index out of bounds");
return this.data[index];
}
// ... другие методы
}
const arr = new FixedArray(3, 1, 2, 3); // ок
const bad = new FixedArray(3, 1, 2); // Ошибка в runtime
5. Современный подход: variadic tuple types (TypeScript 4.0+)
Начиная с TS 4.0 можно использовать spread в кортежах для динамического создания:
// Функция, принимающая ровно 3 числа
function setCoordinates(...args: [number, number, number]) {
// args — это кортеж [number, number, number]
}
setCoordinates(1, 2, 3); // ок
setCoordinates(1, 2); // Ошибка: Expected 3 arguments
// generic подход для произвольной длины
type ArrayOfLength<T, N extends number> =
N extends 0 ? [] :
N extends 1 ? [T] :
N extends 2 ? [T, T] :
N extends 3 ? [T, T, T] :
T[]; // fallback для больших N
Итоговые рекомендации
- Для 2–5 элементов используйте обычный кортеж:
[number, number] - Для защиты от изменений добавляйте
readonly:readonly [number, number] - Для больших фиксированных массивов (до ~50) используйте рекурсивный тип
Tuple - Для очень больших массивов — проверка длины в рантайме через класс или функцию
- Помните: кортежи — это синтаксический сахар, они не дают гарантий на уровне выполнения, только на уровне типов
Надеюсь, теперь стало понятнее! Если остались вопросы — спрашивайте.
<<Использование утилитарных типов из библиотек
Если вам не хочется писать рекурсивные типы вручную, можно воспользоваться готовыми решениями из популярных библиотек.
Вариант 1: TypeScript Utility Types (ts-essentials)
Установите пакет:
npm install ts-essentials
Используйте тип FixedLengthArray:
import { FixedLengthArray } from 'ts-essentials';
type Point = FixedLengthArray<number, 2>; // [number, number]
type RGB = FixedLengthArray<number, 3>; // [number, number, number]
let arr: RGB = [255, 0, 0]; // ок
let bad: RGB = [255, 0]; // Ошибка: Type '[number, number]' is not assignable to type '[number, number, number]'
Вариант 2: Самодельный тип с помощью mapped types
Можно использовать более простой подход без рекурсии — через условные типы и mapped tuples (доступно с TS 4.1):
type FixedArray<T, N extends number> =
{ [K in keyof TupleOf<T, N>]: T };
type TupleOf<T, N extends number, R extends any[] = []> =
R['length'] extends N ? R : TupleOf<T, N, [...R, T]>;
// Тест
type Test = FixedArray<number, 5>; // [number, number, number, number, number]
Вариант 3: Простое решение для небольших длин
Если длина не превышает 10–15, можно просто перечислить все варианты в union:
type ArrayOf3 = [any, any, any];
type ArrayOf4 = [any, any, any, any];
// ... и так далее
Это неэлегантно, но работает и легко читается.
Выбирайте подход, который лучше подходит под вашу задачу. Для большинства случаев readonly кортеж — оптимальное решение.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги
Функции в Python: объявление, аргументы, возврат значений и глобальные переменные