Как типизировать массивы, кортежи и коллекции в TypeScript?

MiddleTypeScript #typescript #arrays #tuples

Вопрос

Как типизировать массивы (T[] и Array<T>), кортежи (tuples) и коллекции вроде Map, Set, Record?

Короткий ответ

T[] и Array<T> — это два равнозначных синтаксиса для типизации массива с элементами одного типа. Кортеж ([T, U, ...]) описывает массив фиксированной длины, где тип каждого элемента задан по позиции. Map<K, V>, Set<T> и Record<K, V> — дженерик-типы для типизации ключ-значение и множеств.

Подробный ответ

T[] vs Array<T> — семантически идентичны, различие чисто синтаксическое. T[] короче и привычнее, Array<T> удобен для сложных типов (избегает лишних скобок) и явно показывает, что это дженерик:

let names: string[] = ['a', 'b']
let ids: Array<number> = [1, 2, 3]
let matrix: number[][] = [[1, 2], [3, 4]] // массив массивов

Кортежи (tuples) — фиксированной длины массивы, где важен и тип, и позиция элемента. В отличие от обычного массива, где все элементы одного типа и длина не ограничена:

let point: [number, number] = [10, 20]
let entry: [string, number, boolean] = ['age', 30, true]

// именованные элементы (только для документации, не влияют на проверку типов)
let range: [start: number, end: number] = [0, 100]

// опциональные и rest-элементы
let cssRule: [property: string, value: string, important?: boolean] = ['color', 'red']
let path: [root: string, ...segments: string[]] = ['/', 'users', '42']

Кортежи часто используются как тип возврата функций, возвращающих несколько значений (например, React useState возвращает [value, setValue]).

Map<K, V> и Set<T> — типизированные версии встроенных коллекций:

const cache: Map<string, number> = new Map()
cache.set('a', 1)

const uniqueIds: Set<number> = new Set([1, 2, 3])

Record<K, V> — utility-тип для объекта, где все ключи одного типа K сопоставлены значениям типа V. По сути — типобезопасная альтернатива { [key: string]: V }, особенно полезная, когда K — union конкретных строковых литералов, потому что тогда TypeScript требует наличия всех ключей:

type Role = 'admin' | 'user' | 'guest'

const permissions: Record<Role, string[]> = {
  admin: ['read', 'write', 'delete'],
  user: ['read', 'write'],
  guest: ['read'],
} // если забыть один из ключей Role — ошибка компиляции

Пример

type Status = 'pending' | 'success' | 'error'

// кортеж как тип возврата, аналог React useState
function useToggle(initial: boolean): [boolean, () => void] {
  let state = initial
  const toggle = () => { state = !state }
  return [state, toggle]
}

const [isOpen, toggleOpen] = useToggle(false)

// Record для строгой карты обработчиков на все статусы
const statusHandlers: Record<Status, (msg: string) => void> = {
  pending: (msg) => console.log('⏳', msg),
  success: (msg) => console.log('✅', msg),
  error: (msg) => console.error('❌', msg),
}

// Map для кэша с типизированными ключами и значениями
const requestCache = new Map<string, Promise<unknown>>()

function getCached<T>(key: string, loader: () => Promise<T>): Promise<T> {
  if (!requestCache.has(key)) requestCache.set(key, loader())
  return requestCache.get(key) as Promise<T>
}

Дополнительные вопросы

  • Чем readonly T[] отличается от обычного T[] и когда его стоит использовать?
  • Как TypeScript выводит тип кортежа против обычного массива при const arr = [1, 'a']?
  • В чём разница между Record<string, T> и индексной сигнатурой { [key: string]: T }?