Спецификация ES2023 теперь включает четыре метода массива — toSorted, toReversed, toSpliced и with, — которые возвращают новые массивы вместо мутации исходного. В React и других UI-библиотеках, полагающихся на неизменяемое (immutable) состояние, эти помощники позволяют разработчикам заменить трюки с оператором spread, которые долгое время были источником багов и лишнего шаблонного кода.

Почему это изменение важно

React решает, нужно ли перерисовывать компонент, сравнивая ссылку на предыдущее состояние с новой. Если ссылка не изменилась, React считает, что ничего не произошло. Классический метод Array.prototype.sort сортирует массив на месте и возвращает ту же самую ссылку, поэтому вызов вроде setTasks(prev => prev.sort(fn)) оставляет React «слепым» к обновлению. Интерфейс застревает на устаревших данных — баг, который слишком часто встречается в реальных проектах.

Разработчики обходили эту проблему, сначала клонируя массив — обычно с помощью оператора spread, — чтобы этап сортировки создавал новую ссылку:

setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

Этот паттерн работает, но он загромождает код и о нем легко забыть. Новые методы ES2023 предлагают прямой и читаемый способ создания нового массива, оставляя оригинал нетронутым.

Четыре метода, не мутирующих массив

  • toSorted(compareFn?) — работает как sort, но возвращает отсортированную копию. Исходный массив не изменяется.
  • toReversed() — заменяет reverse. Возвращает перевернутую копию, сохраняя исходный порядок нетронутым.
  • toSpliced(start, deleteCount, ...items) — аналогичен splice, но без побочных эффектов. Возвращаемый массив отражает вставку или удаление, а исходный остается прежним.
  • with(index, value) — заменяет элемент по индексу index на value и возвращает новый массив. Он заменяет распространенные паттерны с использованием map или оператора spread для замены элемента.

Все четыре метода являются частью стандарта ECMAScript и доступны в текущих версиях основных браузеров и Node.js 20.

Как теперь выглядит код

Сортировка списка

// Before
setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

// After
setTasks(prev => prev.toSorted((a, b) => b.priority - a.priority));

Обновление одного элемента

// Before
setItems(prev =>
  prev.map((item, i) => (i === idx ? newItem : item))
);

// After
setItems(prev => prev.with(idx, newItem));

Разворот массива

setLogs(prev => prev.toReversed());

Удаление элемента

setTags(prev => prev.toSpliced(removeIdx, 1));

Новый синтаксис избавляет от необходимости использовать дополнительные операторы spread или циклы mapping, делая обновление состояния более читаемым и менее подверженным ошибкам.

Кто выиграет, а кто может сомневаться

Разработчики, использующие React, Vue, Redux, Zustand или любой другой фреймворк, ожидающий неизменяемые структуры данных, получают более простую ментальную модель: вызываете метод, получаете новый массив, передаете его в сеттер. Сокращение шаблонного кода также может сэкономить несколько миллисекунд в циклах рендеринга, так как движок избегает создания промежуточной копии перед сортировкой.

Команды с устаревшими браузерами могут столкнуться с необходимостью использования полифиллов. Эти методы отсутствуют в старых версиях Safari или Internet Explorer, поэтому продакшн-сборка, ориентированная на эти платформы, должна включать фолбэк (fallback). Это немного увеличивает размер бандла, но выгода в читаемости кода часто того стоит.

Авторам библиотек может потребоваться обновить определения типов (например, TypeScript), чтобы открыть доступ к новым сигнатурам. Пока эти определения не появятся в официальных пакетах @types, разработчики могут сталкиваться с временными ошибками типизации.

На что обратить внимание в будущем

  • Метрики внедрения — инструменты вроде ESLint вскоре могут добавить правила, которые помечают вызовы мутирующих методов массива в сеттерах состояния, подталкивая разработчиков к использованию новых методов.
  • Исследования производительности — первые бенчмарки показывают, что нативные не мутирующие методы работают быстрее, чем клонирование через spread с последующей мутацией, но реальные данные подтвердят этот эффект.
  • Дальнейшие предложения — комитет ECMAScript продолжает изучать API, которые по умолчанию являются неизменяемыми; отслеживание новых этапов может открыть доступ к еще большему количеству помощников, работающих по тому же принципу.

Итог

Последняя спецификация ECMAScript дает UI-разработчикам встроенный лаконичный способ поддерживать неизменяемость состояния без «гимнастики» с оператором spread, которая приводила к бесчисленным багам. Заменяя sort, reverse, splice и замену по индексу на toSorted, toReversed, toSpliced и with, вы позволяете механизму обнаружения изменений React работать так, как задумано, и делаете свой код более читаемым. Если ваши целевые браузеры поддерживают новые методы — или вы готовы использовать полифиллы — пришло время отказаться от старых паттернов и позволить языку взять основную работу на себя.