구조 분해 할당과 스프레드 연산자
배열·객체에서 필요한 값을 구조 분해하고 스프레드로 복사·결합·함수 인수 전달을 간결하게 처리합니다.
우리는 8장을 통해 let, const로 변수 선언을 개선하고, 화살표 함수로 코드 작성 방식을 더 간결하고 예측 가능하게 만들었습니다.
이제 데이터 처리 방식 자체를 크게 바꿔준 다음 핵심 기능으로 넘어갑니다.
구조 분해 할당(Destructuring Assignment)과 스프레드 연산자(Spread Operator)는 배열이나 객체에서 데이터를 추출하거나, 배열이나 객체를 복사/결합할 때 사용되는 문법입니다.
이 두 기능은 코드를 훨씬 간결하고 가독성 높게 만들어주며, 데이터를 다루는 다양한 상황에서 개발자의 편의성을 극대화합니다.
이번 장에서는 구조 분해 할당으로 값 추출하기, 스프레드 연산자로 배열/객체 다루기를 중심으로 핵심 패턴을 정리합니다.
구조 분해는 필요한 값을 이름으로 꺼내고, 스프레드는 남은 값이나 복사 범위를 펼칩니다. 두 문법 모두 데이터 모양을 코드 표면에 드러냅니다.
- 값을 꺼내고 새 컬렉션을 만드는 순서
구조 분해 할당은 배열이나 객체에서 필요한 값을 바로 꺼냅니다.
- 스프레드 연산자
배열이나 객체의 내용을 펼쳐 전달합니다.
- 구조 분해와 스프레드 정리
배열과 객체 데이터를 꺼내고 조합하는 코드를 간결하게 만듭니다.
- 배열 구조 분해
위치를 기준으로 값을 꺼내 변수에 바로 할당합니다.
- 복사 깊이와 누락 값을 먼저 확인
구조 분해 할당은 필요한 값을 꺼내고, 스프레드 연산자는 기존 값을 복사하거나 합칠 때 사용합니다.
구조 분해 할당
구조 분해 할당과 스프레드 연산자에서는 값의 변화, 실행 순서, 화면 반응을 함께 봅니다.
객체는 property name, 배열은 index position으로 값을 꺼내며 alias, default, rest, nested 패턴을 함께 쓸 수 있다.
| 패턴 | 기준 | 예시 | 주의점 |
|---|---|---|---|
| Object | 속성 이름으로 꺼냄 | const { name } = user | 속성 이름 오타 |
| Array | 순서 위치로 꺼냄 | const [first] = list | 순서 의미가 불명확 |
| Alias | 다른 변수명으로 받음 | { name: userName } | 원래 이름과 새 이름 혼동 |
| Default | 값이 undefined일 때 대체 | { count = 0 } | null에는 적용되지 않음 |
| Rest | 남은 값 묶기 | { id, ...rest } | 얕은 복사임 |
구조 분해 할당 (Destructuring Assignment)은 배열이나 객체의 속성(property)을 해체하여 그 값을 개별 변수에 할당할 수 있게 하는 자바스크립트 표현식입니다.
복잡한 데이터 구조에서 필요한 값만 깔끔하게 추출할 때 매우 유용합니다.
배열 구조 분해 (Array Destructuring)
배열의 각 요소를 해체하여 변수에 할당할 수 있습니다.
const colors = ["red", "green", "blue"];
// 배열 구조 분해 할당
const [firstColor, secondColor, thirdColor] = colors;
console.log(firstColor); // 결과: "red"
console.log(secondColor); // 결과: "green"
console.log(thirdColor); // 결과: "blue"-
일부 값만 추출: 필요한 요소만 선택적으로 가져올 수 있습니다. 건너뛰고 싶은 요소는 쉼표로 비워둡니다.
const numbers = [10, 20, 30, 40, 50]; const [a, , c, , e] = numbers; // 두 번째와 네 번째 요소 건너뛰기 console.log(a); // 결과: 10 console.log(c); // 결과: 30 console.log(e); // 결과: 50 -
기본값 설정: 할당할 값이
undefined일 경우에 대비하여 기본값을 설정할 수 있습니다.const fruits = ["apple"]; const [myFruit, otherFruit = "orange"] = fruits; console.log(myFruit); // 결과: "apple" console.log(otherFruit); // 결과: "orange" (fruits 배열에 두 번째 요소가 없으므로 기본값 사용) const [item1, item2 = "default"] = []; console.log(item1); // 결과: undefined console.log(item2); // 결과: "default" -
나머지 요소 가져오기 (
...rest): 배열의 나머지 요소들을 새로운 배열로 묶을 수 있습니다. (스프레드 연산자와 동일한 문법이지만 여기서는 나머지 파라미터 역할)const scores = [80, 90, 70, 60, 95]; const [math, english, ...restScores] = scores; console.log(math); // 결과: 80 console.log(english); // 결과: 90 console.log(restScores); // 결과: [70, 60, 95] (새로운 배열) -
변수 값 교환: 임시 변수 없이 두 변수의 값을 쉽게 교환할 수 있습니다.
let x = 1; let y = 2; [x, y] = [y, x]; // x와 y의 값을 교환 console.log(x); // 결과: 2 console.log(y); // 결과: 1
객체 구조 분해 (Object Destructuring)
객체의 프로퍼티를 해체하여 변수에 할당할 수 있습니다.
변수 이름은 객체의 프로퍼티 이름과 동일해야 합니다.
const person = {
name: "Alice",
age: 30,
city: "New York"
};
// 객체 구조 분해 할당
const { name, age } = person;
console.log(name); // 결과: "Alice"
console.log(age); // 결과: 30
// console.log(city); // ReferenceError: city is not defined (city는 할당하지 않음)-
다른 이름으로 할당: 객체의 프로퍼티 이름과 다른 이름으로 변수를 선언하고 싶을 때 사용합니다.
const { name: userName, age: userAge } = person; console.log(userName); // 결과: "Alice" console.log(userAge); // 결과: 30 -
기본값 설정: 할당할 프로퍼티가
undefined일 경우에 대비하여 기본값을 설정할 수 있습니다.const { country = "Korea", age } = person; // person 객체에 country 프로퍼티가 없으므로 기본값 사용 console.log(country); // 결과: "Korea" console.log(age); // 결과: 30 -
나머지 프로퍼티 가져오기 (
...rest): 객체의 나머지 프로퍼티들을 새로운 객체로 묶을 수 있습니다.const { name, ...restInfo } = person; console.log(name); // 결과: "Alice" console.log(restInfo); // 결과: { age: 30, city: "New York" } (새로운 객체) -
중첩된 객체 구조 분해: 중첩된 객체에서 깊숙이 있는 프로퍼티를 한 번에 추출할 수 있습니다.
const userProfile = { id: 1, userInfo: { fullName: "Bob Johnson", email: "bob@example.com" }, preferences: { theme: "dark" } }; const { userInfo: { fullName, email }, // userInfo 객체를 분해하여 fullName, email 추출 preferences: { theme: userTheme } // preferences 객체를 분해하여 theme를 userTheme으로 추출 } = userProfile; console.log(fullName); // 결과: "Bob Johnson" console.log(email); // 결과: "bob@example.com" console.log(userTheme); // 결과: "dark" -
함수 매개변수에서의 구조 분해: 함수의 매개변수에서 객체나 배열을 직접 구조 분해하여 사용할 수 있어 코드가 더욱 간결해집니다.
// 객체 매개변수 구조 분해 function displayUser({ name, age }) { console.log(`이름: ${name}, 나이: ${age}`); } const myUser = { name: "철수", age: 25 }; displayUser(myUser); // 결과: 이름: 철수, 나이: 25 // 배열 매개변수 구조 분해 function printCoordinates([x, y]) { console.log(`X: ${x}, Y: ${y}`); } const point = [10, 20]; printCoordinates(point); // 결과: X: 10, Y: 20
스프레드 연산자 ...
스프레드 연산자 (Spread Operator) (...)는 이터러블(iterable) 객체(배열, 문자열 등)나 객체의 내용을 개별 요소로 확장(펼치는)하는데 사용됩니다.
배열, 객체, 함수의 인자 등 다양한 곳에서 활용됩니다.
배열에서의 스프레드 연산자
-
배열 결합 (Concatenation): 여러 배열을 쉽게 결합할 수 있습니다.
const arr1 = [1, 2]; const arr2 = [3, 4]; const combinedArr = [...arr1, ...arr2]; console.log(combinedArr); // 결과: [1, 2, 3, 4] const newArr = [0, ...arr1, 5, ...arr2]; console.log(newArr); // 결과: [0, 1, 2, 5, 3, 4] -
배열 복사 (Shallow Copy): 원본 배열을 변경하지 않고 새로운 배열의 사본을 만들 수 있습니다. (얕은 복사임에 유의)
const original = [1, 2, 3]; const copy = [...original]; console.log(copy); // 결과: [1, 2, 3] console.log(original === copy); // 결과: false (다른 메모리 주소) copy.push(4); console.log(original); // 결과: [1, 2, 3] (원본 불변) -
배열 요소 추가/삽입: 배열의 특정 위치에 요소를 쉽게 추가하거나 삽입할 수 있습니다.
const baseArray = [1, 2, 3]; const newArrayAtStart = [0, ...baseArray]; // 맨 앞에 추가 console.log(newArrayAtStart); // 결과: [0, 1, 2, 3] const newArrayAtMiddle = [1, ...baseArray.slice(1, 2), 99, ...baseArray.slice(2)]; // 중간에 삽입 (복잡) console.log(newArrayAtMiddle); // 결과: [1, 2, 99, 3] // 실용적으로는 splice나 map, filter를 조합하는 경우가 많음 -
이터러블을 배열로 변환: 유사 배열 객체나 다른 이터러블(예: Set, String)을 배열로 쉽게 변환할 수 있습니다.
const myString = "hello"; const charArray = [...myString]; console.log(charArray); // 결과: ["h", "e", "l", "l", "o"] const mySet = new Set([1, 2, 3]); const setToArray = [...mySet]; console.log(setToArray); // 결과: [1, 2, 3]
객체에서의 스프레드 연산자 (ES2018부터)
-
객체 병합 (Object Merging): 여러 객체를 병합하여 새로운 객체를 만들 수 있습니다.
const user = { name: "John", age: 30 }; const address = { city: "Seoul", zip: "12345" }; const details = { job: "Developer" }; const fullProfile = { ...user, ...address, ...details }; console.log(fullProfile); // 결과: { name: 'John', age: 30, city: 'Seoul', zip: '12345', job: 'Developer' }이때 동일한 키가 있으면 뒤에 오는 객체의 값이 덮어씁니다.
const objA = { a: 1, b: 2 }; const objB = { b: 3, c: 4 }; const mergedObj = { ...objA, ...objB }; console.log(mergedObj); // 결과: { a: 1, b: 3, c: 4 } (b가 덮어써짐) -
객체 복사 (Shallow Copy): 원본 객체를 변경하지 않고 새로운 객체의 사본을 만들 수 있습니다. (얕은 복사임에 유의)
const originalObj = { x: 1, y: 2 }; const copyObj = { ...originalObj }; console.log(copyObj); // 결과: { x: 1, y: 2 } console.log(originalObj === copyObj); // 결과: false copyObj.x = 10; console.log(originalObj.x); // 결과: 1 (원본 불변) -
객체 프로퍼티 추가/업데이트: 기존 객체에 새로운 프로퍼티를 추가하거나, 특정 프로퍼티 값을 업데이트할 때 유용합니다.
const product = { id: 1, name: "Laptop", price: 1200 }; const updatedProduct = { ...product, price: 1100, category: "Electronics" }; console.log(updatedProduct); // 결과: { id: 1, name: 'Laptop', price: 1100, category: 'Electronics' }
함수 호출 시 스프레드 연산자 (...args)
함수에 배열의 요소들을 개별적인 인자로 전달할 때 사용합니다.
(이때는 나머지 파라미터와는 다름)
function sum(a, b, c) {
return a + b + c;
}
const numbersToSum = [1, 2, 3];
const result = sum(...numbersToSum); // 배열 요소를 개별 인자로 펼쳐서 전달
console.log(result); // 결과: 6서버에서 받은 JSON 데이터를 화면 상태로 바꾸는 과정에서도 구조 분해와 스프레드는 함께 쓰입니다.
HTTP JSON을 받은 뒤 구조 분해로 필요한 필드만 꺼내고, 스프레드로 기존 상태를 얕게 복사해 렌더 입력을 만든다.
| 단계 | 하는 일 | 문법 | 검토할 것 |
|---|---|---|---|
| Receive | JSON 응답을 받음 | await res.json() | 응답 구조 검증 |
| Pick | 필요 필드를 꺼냄 | const { items } = data | 없는 필드 기본값 |
| Rename | 화면에서 쓸 이름으로 변환 | { id: userId } | 의미 있는 변수명 |
| Copy | 기존 상태를 얕게 복사 | { ...state, items } | 중첩 객체 mutation 주의 |
| Render input | DOM이나 컴포넌트에 전달 | setState(next) | 원본 응답과 화면 상태 분리 |
구조 분해와 스프레드 정리
이번 장에서는 ES2015(ES6)에서 도입된 매우 강력하고 유용한 문법인 구조 분해 할당(Destructuring Assignment)과 스프레드 연산자(Spread Operator)에 대해 심도 있게 학습했습니다.
구조 분해 할당은 배열과 객체에서 필요한 값만 꺼낼 때 사용합니다.
스프레드 연산자는 배열 결합, 얕은 복사, 객체 병합, 함수 인자 전달처럼 값을 펼쳐 전달해야 하는 상황에서 자주 쓰입니다.
이 두 기능은 데이터를 꺼내고 합치는 코드를 짧게 만들지만, 원본 변경 여부와 얕은 복사 범위는 항상 함께 확인해야 합니다.
예제를 직접 바꿔 보며 배열과 객체에서 어떤 값이 새로 만들어지는지 확인해 보세요.
구조 분해와 스프레드는 객체와 배열을 직접 바꾸지 않고 필요한 값만 꺼내거나 새 값으로 조합하는 데 자주 쓰입니다.
두 문법은 ...처럼 비슷해 보이는 기호도 있지만, 값 추출과 값 복사의 위치가 다르므로 목적을 먼저 나누면 쉽다.
| 목적 | 구조 분해 | 스프레드 | 주의점 |
|---|---|---|---|
| 값 꺼내기 | const { name } = user | 해당 없음 | 없는 값 기본값 처리 |
| 이름 바꾸기 | { name: label } | 해당 없음 | 원래 속성명과 변수명 구분 |
| 남은 값 묶기 | { id, ...rest } | rest property | 얕은 복사 |
| 값 펼치기 | 해당 없음 | { ...user, active:true } | 뒤 값이 앞 값을 덮음 |
| 배열 조합 | [first, ...others] | [...a, ...b] | 중첩 배열은 그대로 공유 |
마지막으로 상태나 응답 데이터를 다룰 때는 어떤 값이 추출되고 어떤 값이 새 객체로 복사되는지 한 흐름으로 확인합니다.
값을 꺼낼지, 새 객체나 배열을 만들지, 중첩 값을 안전하게 복사할지에 따라 문법 선택이 달라진다.
| 질문 | 쓸 문법 | 좋은 예 | 위험 신호 |
|---|---|---|---|
| 필드만 꺼내나 | 구조 분해 | const { id } = item | 필드가 없을 때 처리 없음 |
| 이름을 바꾸나 | alias | { id: itemId } | 변수 의미가 흐림 |
| 나머지를 보관하나 | rest | { id, ...rest } | rest도 얕은 복사임 |
| 새 값을 합치나 | spread | { ...prev, done:true } | 덮어쓰기 순서 착각 |
| 깊은 복사가 필요한가 | structuredClone 등 별도 처리 | 중첩 변경 방지 | spread가 deep copy라고 오해 |
구조 분해는 값을 꺼내고 스프레드는 새 배열·객체를 만들기 쉽지만, 중첩 객체까지 자동으로 복제하지는 않는다.
| 작업 | 겉보기 | 실제 경계 | 주의할 버그 |
|---|---|---|---|
| Object spread | 새 객체 생성 | 1단계 속성만 새로 복사 | 중첩 객체는 같은 참조 |
| Array spread | 새 배열 생성 | 요소 참조는 그대로 | 객체 요소 mutation 공유 |
| Destructure | 필드 값을 변수로 꺼냄 | 꺼낸 값이 참조일 수 있음 | 값을 바꾸면 원본에 영향 가능 |
| Nested update | 중첩 단계마다 새 객체 필요 | { ...a, b:{ ...a.b } } | 한 단계만 복사 |
| Deep copy | 깊은 복사가 필요할 때 별도 도구 | structuredClone | 함수/특수 객체 처리 주의 |
구조 분해와 스프레드는 객체·배열의 모양을 읽기 쉽게 다루지만, 얕은 복사 한계를 알아야 상태 변경 버그를 피한다.
| 경계 | 안전한 경우 | 위험한 경우 | 대응 |
|---|---|---|---|
| Primitive | 숫자와 문자열을 꺼내 사용 | 타입 변환만 주의 | 그대로 사용 가능 |
| Top-level object | 1단계 속성만 바꾸기 | 중첩 객체를 함께 수정 | 수정할 단계마다 복사 |
| Array item | 원시값 배열 변환 | 객체 배열의 item mutation | map으로 새 item 생성 |
| Merge order | 뒤 값이 앞 값을 덮음 | 기본값과 사용자값 순서 착각 | 우선순위를 코드에 드러냄 |
| State update | 불변 업데이트가 필요한 UI 상태 | 원본을 직접 수정 | 새 참조와 새 중첩 참조 생성 |