자바스크립트 배열의 toSpliced() 함수

자바스크립트 배열의 toSpliced() 함수

자바스크립트에서 배열의 원소를 삭제하거나 추가하거나 교체할 때 가장 많이 사용되는 메서드가 바로 splice()인데요. 이 메서드에는 한 가지 골치 아픈 문제가 있습니다. 바로 원본 배열을 직접 변경해버린다는 점이죠 😅

원본 배열이 바뀌면 안 되는 상황에서는 매번 배열을 복제한 다음에 splice()를 호출해야 했는데 이게 꽤 번거로웠습니다. ES2023에서 이 불편함을 해소해주는 toSpliced() 함수가 등장했는데요. 이번 글에서는 toSpliced() 함수의 사용법과 splice()와의 차이점에 대해서 알아보겠습니다.

splice() 복습

toSpliced()를 이해하려면 먼저 splice()가 어떻게 동작하는지 알아야 하는데요. 간단히 복습해볼까요?

splice() 함수는 첫 번째 인자로 시작 인덱스, 두 번째 인자로 삭제할 개수, 그리고 세 번째 인자부터는 추가할 값을 받습니다.

const fruits = ["Apple", "Banana", "Coconut", "Grape"];

// 인덱스 1에서 2개 삭제
const removed = fruits.splice(1, 2);

console.log(removed); // ["Banana", "Coconut"] ← 삭제된 원소
console.log(fruits); // ["Apple", "Grape"] ← 원본이 변경됨!

여기서 핵심은 두 가지입니다. splice()가 원본 배열을 직접 변경한다는 점, 그리고 반환값이 삭제된 원소가 담긴 배열이라는 점이죠.

이런 동작 방식 때문에 React 같은 프레임워크에서 상태를 관리할 때 특히 주의가 필요했습니다.

splice() 함수에 대한 좀 더 자세한 내용은 자바스크립트 배열의 slice()와 splice() 함수를 참고바랍니다.

toSpliced() 함수

toSpliced() 함수는 splice()와 사용법이 거의 동일합니다. 첫 번째 인자로 시작 인덱스, 두 번째 인자로 삭제할 개수, 세 번째 인자부터는 추가할 값을 받는 것까지 똑같죠.

const fruits = ["Apple", "Banana", "Coconut", "Grape"];
const result = fruits.toSpliced(1, 2);

console.log(result); // ["Apple", "Grape"]
console.log(fruits); // ["Apple", "Banana", "Coconut", "Grape"] ← 원본 그대로!

그런데 결정적인 차이가 두 가지 있습니다.

우선, 원본 배열을 건드리지 않습니다. toSpliced()를 호출해도 원래 배열은 변하지 않고 변경이 적용된 새로운 배열이 반환됩니다.

또한, 반환값이 다릅니다. splice()는 삭제된 원소의 배열을 반환하지만 toSpliced()는 변경이 적용된 새로운 배열 전체를 반환합니다.

이 차이를 표로 정리하면 다음과 같습니다.

splice()toSpliced()
원본 변경O (원본 변경)X (원본 유지)
반환값삭제된 원소의 배열변경된 새 배열

원소 삭제

먼저 toSpliced()로 배열에서 원소를 삭제하는 방법부터 살펴볼까요?

배열의 특정 위치에서 원소를 삭제하려면 시작 인덱스와 삭제할 개수를 넘겨주면 됩니다.

const colors = ["빨강", "주황", "노랑", "초록", "파랑"];

// 인덱스 1에서 2개 삭제
colors.toSpliced(1, 2);
// ["빨강", "초록", "파랑"]

두 번째 인자를 생략하면 시작 인덱스부터 배열 끝까지 모두 삭제됩니다.

const colors = ["빨강", "주황", "노랑", "초록", "파랑"];

colors.toSpliced(2);
// ["빨강", "주황"]

시작 인덱스로 음수를 넘기면 배열 끝에서부터 위치를 셉니다.

const colors = ["빨강", "주황", "노랑", "초록", "파랑"];

// 끝에서 2번째부터 1개 삭제
colors.toSpliced(-2, 1);
// ["빨강", "주황", "노랑", "파랑"]

원소 추가

세 번째 인자부터 추가할 값들을 넘기면 해당 위치에 새로운 원소를 삽입할 수 있습니다. 이때 삭제 개수를 0으로 설정하면 아무것도 삭제하지 않고 추가만 합니다.

const months = ["1월", "2월", "4월", "5월"];

// 인덱스 2에 "3월" 삽입 (삭제 없음)
months.toSpliced(2, 0, "3월");
// ["1월", "2월", "3월", "4월", "5월"]

여러 개의 원소를 한 번에 추가할 수도 있고요.

const nums = [1, 5];

// 인덱스 1에 2, 3, 4 삽입
nums.toSpliced(1, 0, 2, 3, 4);
// [1, 2, 3, 4, 5]

배열에 원소를 추가하는 다른 방법에 대해서는 자바스크립트 배열에 원소 추가하기: push()와 unshift() 메서드를 참고바랍니다.

원소 교체

삭제할 개수와 추가할 값을 동시에 지정하면 원소를 교체하는 효과를 낼 수 있습니다.

const seasons = ["봄", "여름", "장마", "겨울"];

// 인덱스 2의 "장마"를 "가을"로 교체
seasons.toSpliced(2, 1, "가을");
// ["봄", "여름", "가을", "겨울"]

하나를 삭제하고 여러 개를 추가하거나, 여러 개를 삭제하고 하나로 대체하는 것도 물론 가능합니다.

const letters = ["a", "b", "c", "d", "e"];

// 인덱스 1에서 3개 삭제 후 "X" 하나로 대체
letters.toSpliced(1, 3, "X");
// ["a", "X", "e"]

기존 패턴과 비교

toSpliced()가 등장하기 전에는 원본 배열을 보존하면서 원소를 조작하려면 꽤 번거로운 코드를 작성해야 했는데요.

가장 흔한 패턴은 배열을 복제한 뒤 splice()를 호출하는 것이었습니다.

const arr = ["a", "b", "c", "d"];

// 예전 방식: 복제 후 splice
const copy = [...arr];
copy.splice(1, 2, "X");
console.log(copy); // ["a", "X", "d"]

slice()와 전개 연산자를 조합하는 패턴도 자주 사용되었고요.

const arr = ["a", "b", "c", "d"];

// 예전 방식: slice + spread
const result = [...arr.slice(0, 1), "X", ...arr.slice(3)];
console.log(result); // ["a", "X", "d"]

toSpliced()를 사용하면 이 모든 걸 한 줄로 깔끔하게 처리할 수 있습니다.

const arr = ["a", "b", "c", "d"];

const result = arr.toSpliced(1, 2, "X");
console.log(result); // ["a", "X", "d"]

코드가 간결해질 뿐만 아니라 의도도 훨씬 명확하게 드러나죠.

React 상태 관리

toSpliced()가 특히 빛을 발하는 곳이 바로 React 같은 프레임워크에서의 상태 관리입니다. React에서는 상태를 직접 변경하면 안 되기 때문에 배열 상태를 업데이트할 때 항상 새로운 배열을 만들어야 하거든요.

예전에는 이렇게 작성해야 했습니다.

const [items, setItems] = useState(["사과", "바나나", "포도"]);

// 인덱스 1의 원소 삭제
setItems((prev) => [...prev.slice(0, 1), ...prev.slice(2)]);

// 인덱스 1에 원소 추가
setItems((prev) => [...prev.slice(0, 1), "오렌지", ...prev.slice(1)]);

// 인덱스 1의 원소 교체
setItems((prev) => [...prev.slice(0, 1), "오렌지", ...prev.slice(2)]);

toSpliced()를 사용하면 훨씬 읽기 편해집니다.

const [items, setItems] = useState(["사과", "바나나", "포도"]);

// 인덱스 1의 원소 삭제
setItems((prev) => prev.toSpliced(1, 1));

// 인덱스 1에 원소 추가
setItems((prev) => prev.toSpliced(1, 0, "오렌지"));

// 인덱스 1의 원소 교체
setItems((prev) => prev.toSpliced(1, 1, "오렌지"));

삭제, 추가, 교체를 모두 한 줄로 표현할 수 있으니 코드 리뷰할 때도 의도를 파악하기가 한결 수월합니다.

다른 비파괴 메서드들

toSpliced()는 혼자 등장한 것이 아닙니다. ES2023의 “Change Array by Copy” 제안을 통해 기존의 원본 변경 메서드에 대응하는 비파괴 버전이 한꺼번에 추가되었는데요.

toSorted()sort()의 비파괴 버전으로, 원본을 건드리지 않고 정렬된 새 배열을 반환합니다.

const nums = [3, 1, 2];
nums.toSorted(); // [1, 2, 3]
console.log(nums); // [3, 1, 2] ← 원본 그대로

배열 정렬에 대한 자세한 내용은 자바스크립트 배열 정렬: sort()와 toSorted() 함수를 참고바랍니다.

toReversed()reverse()의 비파괴 버전으로, 원본을 건드리지 않고 뒤집어진 새 배열을 반환합니다.

const letters = ["a", "b", "c"];
letters.toReversed(); // ["c", "b", "a"]
console.log(letters); // ["a", "b", "c"] ← 원본 그대로

with()는 대괄호를 이용한 인덱스 할당(arr[i] = value)의 비파괴 버전으로, 특정 인덱스의 값만 바꾼 새 배열을 반환합니다.

const colors = ["빨강", "초록", "파랑"];
colors.with(1, "노랑"); // ["빨강", "노랑", "파랑"]
console.log(colors); // ["빨강", "초록", "파랑"] ← 원본 그대로

이 네 가지 메서드는 모두 인터넷 익스플로러를 제외한 대부분의 모던 브라우저에서 사용이 가능하며, Node.js에서는 v20부터 사용이 가능합니다.

마치며

이상으로 자바스크립트의 toSpliced() 함수에 대해서 살펴보았습니다. splice() 함수와 사용법은 거의 동일하면서도 원본 배열을 건드리지 않는다는 점이 핵심이었는데요. 특히 React 같은 프레임워크에서 불변 상태 관리가 중요해지면서 toSpliced() 같은 비파괴 메서드의 가치가 점점 높아지고 있습니다.

배열을 복제한 뒤 splice()를 호출하는 패턴을 아직 쓰고 계신다면 toSpliced()로 바꿔보시는 건 어떨까요? 코드도 간결해지고 원본 배열이 변경되는 실수도 원천적으로 막을 수 있을 겁니다.

toSpliced() 함수에 대한 더 자세한 내용은 MDN 공식 문서에서 확인하실 수 있습니다. 배열에서 원소를 제거하는 다른 방법이 궁금하시다면 자바스크립트 배열의 원소 제거하기: pop()와 shift() 메서드도 함께 읽어보시면 좋겠습니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord