Record и Tuple (proposal)

Record (#{ }) и Tuple (#) — предложение TC39 (Stage 2) для добавления в JavaScript встроенных примитивных, глубоко иммутабельных структур данных, сравниваемых по значению, а не по ссылке.

Зачем нужно

Одна из главных проблем JavaScript — сравнение объектов и массивов по ссылке: {a:1} !== {a:1}. Это создаёт проблемы в React (лишние ре-рендеры), при сравнении состояния и в иммутабельных структурах данных. Record и Tuple решают это на уровне языка, предлагая примитивную семантику для составных данных.

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

  • Ключи Map (объекты нельзя использовать как уникальные ключи без хака)
  • Сравнение состояния в React/Vue без глубокого equals
  • Конфигурационные объекты, которые должны быть неизменяемы
  • Иммутабельное программирование без библиотек (Immer, Immutable.js)

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

Синтаксис (proposal, не в продакшне)

// Record — иммутабельный объект
const point = #{ x: 1, y: 2 };
const config = #{ host: 'localhost', port: 8080 };

// Tuple — иммутабельный массив
const coords = #[1, 2, 3];
const rgb = #[255, 0, 0];

// Сравнение по значению (!)
console.log(#{ x: 1 } === #{ x: 1 }); // true (в отличие от обычных объектов!)
console.log(#[1, 2] === #[1, 2]);       // true

// Обычные объекты
console.log({ x: 1 } === { x: 1 });   // false

Иммутабельность

const record = #{ a: 1, b: 2 };

// Нельзя мутировать — TypeError
// record.a = 10;
// record.c = 3;

// Создание нового Record с изменениями (spread работает)
const updated = #{ ...record, a: 10 };
console.log(updated === #{ a: 10, b: 2 }); // true
console.log(record === #{ a: 1, b: 2 });   // true (оригинал не изменился)

Ограничения proposal

// Только примитивы и другие Record/Tuple внутри
const valid = #{ x: 1, arr: #[1, 2, 3] };
// const invalid = #{ fn:  => {} }; // TypeError — функции не допускаются
// const invalid2 = #{ obj: { a: 1 } }; // TypeError — обычные объекты не допускаются

// Методы через глобальный Record и Tuple
const r = #{ a: 1, b: 2 };
Record.keys(r);    // ['a', 'b']
Record.values(r);  // [1, 2]

const t = #[1, 2, 3];
Tuple.from([4, 5, 6]); // #[4, 5, 6]
t.pushed(4);           // #[1, 2, 3, 4] (возвращает новый Tuple)

Как сейчас (до стандартизации)

// Аналог через Object.freeze
const record = Object.freeze({ x: 1, y: 2 });
// Но сравнение по ссылке — этот паттерн не решает проблему ===

// Сравнение через JSON (ограничено, но работает для простых случаев)
const equals = (a, b) => JSON.stringify(a) === JSON.stringify(b);
console.log(equals({ x: 1 }, { x: 1 })); // true

// Библиотека Immer для иммутабельных обновлений
import { produce } from 'immer';
const state = { count: 0 };
const newState = produce(state, draft => { draft.count++; });

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

  • Путаница со Stage — на 2026 год Record и Tuple находятся на Stage 2 TC39, не реализованы в браузерах. Не используйте в продакшне без полифила.
  • Ожидание полной замены объектов — Record/Tuple не заменяют объекты. Они примитивы, не имеют методов, не поддерживают функции как значения.
  • Object.freeze не то же самоеfreeze не меняет семантику сравнения. Только поверхностная неизменяемость.

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

Ресурсы