ES2023 명세에는 원본을 변경(mutate)하는 대신 새로운 배열을 반환하는 네 가지 배열 메서드인 toSorted, toReversed, toSpliced, with가 포함되었습니다. 불변(immutable) 상태에 의존하는 React 및 기타 UI 라이브러리에서, 이 헬퍼 메서드들은 개발자들이 오랫동안 버그와 보일러플레이트(boilerplate)의 원인이 되었던 스프레드 연산자(spread operator) 트릭을 대체할 수 있게 해줍니다.

변화가 중요한 이유

React는 이전 상태의 참조(reference)와 새로운 참조를 비교하여 컴포넌트를 리렌더링할지 결정합니다. 참조가 변경되지 않으면 React는 아무것도 변하지 않았다고 가정합니다. 클래식한 Array.prototype.sort 메서드는 배열을 제자리(in-place)에서 정렬하고 동일한 참조를 반환하기 때문에, setTasks(prev => prev.sort(fn))와 같은 호출은 React가 업데이트를 감지하지 못하게 만듭니다. 이로 인해 UI가 오래된 데이터(stale data)에 멈춰 있게 되는데, 이는 실제 프로젝트에서 매우 빈번하게 발생하는 버그입니다.

개발자들은 정렬 단계에서 새로운 참조가 생성되도록 먼저 배열을 복제(보통 스프레드 연산자 사용)하는 방식으로 이 문제를 해결해 왔습니다.

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

이 패턴도 작동하지만, 코드가 지저분해지고 실수로 빠뜨리기 쉽습니다. 새로운 ES2023 메서드는 원본을 건드리지 않으면서 새로운 배열을 생성하는 직접적이고 읽기 쉬운 방법을 제공합니다.

네 가지 비변이(non-mutating) 메서드

  • toSorted(compareFn?)sort와 유사하게 동작하지만 정렬된 복사본을 반환합니다. 원본 배열은 변경되지 않습니다.
  • toReversed()reverse를 대체합니다. 원본 순서를 유지하면서 뒤집힌 복사본을 반환합니다.
  • toSpliced(start, deleteCount, ...items) – 부작용(side effects) 없이 splice와 동일하게 동작합니다. 반환된 배열은 삽입 또는 삭제를 반영하며, 원본은 그대로 유지됩니다.
  • with(index, value)index 위치의 요소를 value로 교체하고 새로운 배열을 반환합니다. map이나 스프레드 연산자를 사용해 요소를 교체하던 일반적인 패턴을 대체합니다.

네 가지 메서드 모두 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));

새로운 문법은 추가적인 스프레드 연산자나 매핑 루프의 필요성을 없애주어, 상태 업데이트를 더 읽기 쉽게 만들고 오류 발생 가능성을 줄여줍니다.

수혜자와 망설이는 이들

React, Vue, Redux, Zustand 또는 불변 데이터 구조를 사용하는 프레임워크를 사용하는 개발자는 더 명확한 멘탈 모델을 가질 수 있습니다. 메서드를 호출하고, 새로운 배열을 받아, 세터(setter)에 전달하기만 하면 됩니다. 또한, 엔진이 정렬 전 중간 복사본을 생성하는 과정을 피할 수 있어 보일러플레이트 감소로 인해 렌더링 사이클에서 몇 밀리초를 단축할 수도 있습니다.

레거시 브라우저를 사용하는 팀은 폴리필(polyfills)을 포함해야 할 수도 있습니다. 이 메서드들은 오래된 버전의 Safari나 Internet Explorer에는 존재하지 않으므로, 해당 플랫폼을 타겟팅하는 프로덕션 빌드에는 폴백(fallback)을 번들링해야 합니다. 이는 번들 크기에 약간의 부담을 주지만, 가독성 향상이라는 이득과 비교하면 충분히 가치 있는 트레이드오프입니다.

라이브러리 제작자는 새로운 시그니처를 노출하기 위해 타입 정의(예: TypeScript)를 업데이트해야 할 수도 있습니다. 해당 정의가 공식 @types 패키지에 반영될 때까지 개발자들은 일시적인 타입 에러를 겪을 수 있습니다.

향후 주목할 점

  • 채택 지표 – ESLint와 같은 도구에서 상태 세터 내의 가변 배열 호출을 찾아내는 규칙을 곧 추가하여, 개발자들이 새로운 메서드를 사용하도록 유도할 수 있습니다.
  • 성능 연구 – 초기 벤치마크에 따르면 네이티브 비변이 메서드가 스프레드 연산자로 복제한 후 가변 작업을 수행하는 것보다 빠르다는 결과가 있지만, 실제 환경에서의 데이터가 그 영향을 확정 지을 것입니다.
  • 추가 제안 – ECMAScript 위원회는 '기본적으로 불변인(immutable-by-default)' API를 계속 탐색하고 있습니다. 향후 단계들을 주시하면 동일한 패턴에 부합하는 더 많은 헬퍼 메서드를 발견할 수 있을 것입니다.

요약

최신 ECMAScript 명세는 UI 개발자에게 수많은 버그를 일으켰던 스프레드 연산자 활용 기술 없이도 상태를 불변으로 유지할 수 있는 내장된 간결한 방법을 제공합니다. sort, reverse, splice 및 인덱스 기반 교체를 toSorted, toReversed, toSpliced, with로 교체함으로써, React의 변경 감지가 의도대로 작동하게 하고 코드를 더 읽기 쉽게 만들 수 있습니다. 타겟 브라우저가 새 메서드를 지원하거나 폴리필을 사용할 의향이 있다면, 이제 오래된 패턴을 버리고 언어가 복잡한 작업을 대신 처리하도록 할 때입니다.