Variadic Tuple Types

Variadic Tuple Types (вариативные кортежи) — возможность TypeScript 4.0 использовать spread-синтаксис ...T в позиции типа кортежа, позволяя создавать типы, которые объединяют, разбивают или трансформируют кортежи с сохранением полной типовой информации.

Зачем нужно

До TS 4.0 типизировать функции вроде concat, prepend или curry было невозможно без перегрузок для каждой длины кортежа. Variadic tuple types решают это обобщённо: TypeScript сохраняет типы каждого элемента при объединении кортежей.

Где используется

  • Типизация concat/prepend/append для кортежей
  • Curry и partial application функций
  • Compose/pipe с полными типами
  • HOF обёртки, сохраняющие параметры исходной функции

Основной контент

Базовый синтаксис spread в кортежах

type Concat<T extends unknown, U extends unknown> = [...T, ...U];

type AB = Concat<[string, number], [boolean, Date]>;
// [string, number, boolean, Date]

type Prepend<T, Arr extends unknown> = [T, ...Arr];
type Append<Arr extends unknown, T> = [...Arr, T];

type P = Prepend<string, [number, boolean]>; // [string, number, boolean]
type A = Append<[string, number], boolean>;  // [string, number, boolean]

Извлечение head и tail

type Head<T extends unknown> =
  T extends [infer H, ...unknown] ? H : never;

type Tail<T extends unknown> =
  T extends [unknown, ...infer Rest] ? Rest : never;

type H = Head<[1, 2, 3]>; // 1
type T = Tail<[1, 2, 3]>; // [2, 3]

type Last<T extends unknown> =
  T extends [...unknown, infer L] ? L : never;

type L = Last<[1, 2, 3]>; // 3

Типизация concat-функции

function concat<T extends unknown, U extends unknown>(
  a: [...T],
  b: [...U]
): [...T, ...U] {
  return [...a, ...b];
}

const result = concat([1, "hello"], [true, new Date]);
// [number, string, boolean, Date] — точные типы!
result[0]; // number
result[2]; // boolean

Spread в Parameters и ReturnType

// Типизация curry с variadic tuples
type Curry<Args extends unknown, R> =
  Args extends [infer First, ...infer Rest]
    ? (arg: First) => Curry<Rest, R>
    : R;

type CurriedAdd = Curry<[number, number], number>;
// (arg: number) => (arg: number) => number

Labeled tuple elements (TS 4.0+)

// Имена элементов кортежа для лучшей документации
type Range = [start: number, end: number];
type Point3D = [x: number, y: number, z: number];

function createRange(start: number, end: number): Range {
  return [start, end];
}

// В IDE при hover показывает: [start: number, end: number]

Практический пример: обёртка с логированием

function withLogging<Args extends unknown, R>(
  fn: (...args: Args) => R,
  label: string
): (...args: Args) => R {
  return (...args: Args): R => {
    console.log(`[${label}] args:`, args);
    const result = fn(...args);
    console.log(`[${label}] result:`, result);
    return result;
  };
}

function add(a: number, b: number): number { return a + b; }
const loggedAdd = withLogging(add, "add");
loggedAdd(1, 2); // типобезопасно: (a: number, b: number) => number

Частые ошибки

  • Использовать T вместо [...T] в параметрах функции — [...T] инструктирует TypeScript сохранять кортежные типы, а не расширять до массива.
  • Spread только в одной позиции — TypeScript разрешает произвольное количество spreads в кортеже, но не два rest-элемента [...A, ...B, ...C] — только последовательные фиксированные + один rest в конце.
  • Путать с JavaScript spread — variadic tuple types — это конструкция системы типов, а не рантайм.
  • Ожидать вывод без явных [...T] — без явного [...T] в сигнатуре TypeScript может вывести (string | number) вместо кортежа.

Связанные темы

Ресурсы