Спецификация 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 работать так, как задумано, и делаете свой код более читаемым. Если ваши целевые браузеры поддерживают новые методы — или вы готовы использовать полифиллы — пришло время отказаться от старых паттернов и позволить языку взять основную работу на себя.
