[JavaScript] 비구조화 할당(Destructuring Assignment)

정의

ECMAScript6(2015)에서 새로 추가된 비구조화 할당(Destructuring Assignment)이란 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 자바스크립트 표현식(expression)이다.

기본 문법(배열)

const animalList = ["CAT", "DOG", "TIGER"];
const cat = animalList[0];
const dog = animalList[1];
const tiger = animalList[2];
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // TIGER

animalList는 “CAT”, “DOG”, “TIGER” 값을 가지고 있는 배열이다. 이 배열의 값을 각각 변수에 할당 하려면 위처럼 각각 하나씩 지정해 줘야 한다. 번거로운 작업이며, 코드도 복잡해보이는 단점이 있다.

const [cat, dog, tiger] = ["CAT", "DOG", "TIGER"];
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // TIGER

비구조화 할당을 이용하면 위처럼 간단하게 작성할 수 있다. 좌항이 호출될 변수명 집합, 우항이 할당할 값이다. 좌항의 각 요소에는 같은 index를 가지는 배열값이 할당된다.

나머지 패턴

const [cat, ...rest] = ["CAT", "DOG", "TIGER"];
console.log(cat); // CAT
console.log(rest); // ["DOG", "TIGER"]

전개연산자(...)를 활용하면 좌항에서 명시적으로 할당되지 않는 나머지 배열 값을 사용할 수 있다.

기본 문법(객체)

const animals = {
  cat: "CAT",
  dog: "DOG",
  tiger: "TIGER"
};
const cat = animals.cat;
const dog = animals.dog;
const tiger = animals.tiger;
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // TIGER

객체도 배열과 마찬가지로 일일히 값을 따로 넣어주려면 번거롭다.

const { cat, dog, tiger } = {
  cat: "CAT",
  dog: "DOG",
  tiger: "TIGER"
};
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // TIGER

위와 같이 작성하면 비구조화 할당을 수행하며, 배열의 경우 좌항의 index값에 값에 할당되었다면, 객체는 같은 key에 있는 값이 담긴다.

나머지 패턴

const { cat, ...rest } = {
  cat: "CAT",
  dog: "DOG",
  tiger: "TIGER"
}
console.log(cat); // CAT
console.log(rest); // {dog: "DOG", tiger: "TIGER"}

배열과 마찬가지로 객체도 나머지 패턴이 있다.

원래의 key 대신 다른 변수명 사용

const { cat: catName, dog: dogName, ...rest } = {
  cat: "CAT",
  dog: "DOG",
  tiger: "TIGER",
  monkey: "MONKEY"
}
console.log(catName); // CAT
console.log(dogName); // DOG
console.log(rest); // {tiger: "TIGER", monkey: "MONKEY"}

좌항의 변수에 다른이름으로 사용할 변수명을 대입하면 되며, 나머지 값을 뜻하하는 전개연산자는 우항의 key에 영향을 받지 않으므로 ...rest: restName이라는 표현식은 무의미 하며, 에러가 난다.

우항의 key 값이 변수명으로 사용 불가 경우

const { 'cat-name', 'dog name' } = {
  'cat-name': "CAT",
  'dog name': "DOG"
}
// error

좌항으로 전달 받는 key 값이 'cat-name'같이 사용 불가능한 문자열이 있는 경우 에러를 호출한다. 이럴 경우는 아래와 같은 방식으로 비구조화 할 수 있다.

const key = 'dog name';
const { 'cat-name': cat_name, [key]: dog_name } = {
  'cat-name': "CAT",
  'dog name': "DOG"
}
console.log(cat_name); // CAT
console.log(dog_name); // DOG

다만 이 경우 'cat-name'과 매칭할 변수명 cat_name을 작성하지 않으면 에러가 발생한다.

객체 비구조화시 변수 선언 키워드가 없는 경우

let cat,
    dog;

// { cat, dog } = { cat: "CAT", dog: "DOG" } // error
({ cat, dog } = { cat: "CAT", dog: "DOG" }) // 괄호로 감싸줘야 함
console.log(cat); // CAT
console.log(dog); // DOG

객체 비구조화시 변수 선언 키워드가 없을 경우 소괄호를 사용하여 감싸줘야 한다. 감싸주지 않으면 에러가 난다.

기본값 할당

배열의 기본값 할당

const [cat, dog, tiger] = ["CAT", "DOG"];
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // undefined

비구조화의 범위를 벗어나는 값 할당을 시도하면 undefined를 반환한다. 이럴 경우를 방지하기 위해 아래처럼 호출될 변수명에 기본값을 할당할 수 있다.

const [cat, dog, tiger = "TIGER"] = ["CAT", "DOG"];
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // TIGER

객체의 기본값 할당

const { cat, dog, tiger = "TIGER" } = {
  cat: "CAT",
  dog: "DOG"
};
console.log(cat); // CAT
console.log(dog); // DOG
console.log(tiger); // TIGER

배열과 마찬가지로 객체도 기본값을 지원한다.

const { monkey: monkey_name = 'MONKEY' } = {};
console.log(monkey_name); // MONKEY

위 코드처럼 객체의 key에 새로운 변수명을 할당하는 방식에도 기본 기본값 할당을 사용할 수 있다.

복사

전개연산자를 사용하여 배열, 객체의 깊은 복사를 할 수 있다.

배열의 깊은 복사

let arr = [1, 2, 3];
let copy1 = arr;
let [...copy2] = arr;
let copy3 = [...arr];

arr[0] = 'String';
console.log(arr); // [ 'String', 2, 3 ]
console.log(copy1); // [ 'String', 2, 3 ]
console.log(copy2); // [ 1, 2, 3 ]
console.log(copy3); // [ 1, 2, 3 ]

얕은 복사인 copy1arr를 참조하기 때문에 0번째 요소가 같이 수정되었지만, 전개연산자를 사용한 copy2copy3은 깊은 복사가 되었기 때문에 0번째 요소가 변경되지 않았다.

객체의 깊은 복사

객체 역시 전개연산자로 깊은 복사를 사용할 수 있다. 무엇보다 강력한 점은 복사와 함께 새로운 값을 할당할 수 있다는 점이다.

let prevState = {
  name: "foo",
  birth: "1995-01-01",
  age: 25
};
let state = {
  ...prevState,
  age: 26
};

console.log(state); // {name: "foo", birth: "1995-01-01", age: 26}

위와 같이 ...prevState를 사용하여 기존 객체를 복사함과 동시에 age에 새로운 값을 할당했다. 리액트의 props나 state처럼 이전 정보를 이용하는 경우 유용하게 사용할 수 있다.

함수에서의 비구조화 할당

함수의 파라미터 부분에서도 비구조화 할당을 사용할 수 있다. 이러한 문법은 특히 API 응답값을 처리하는데에 유용하게 사용된다.

function renderUser({name, age, addr}){
  console.log(name);
  console.log(age);
  console.log(addr);
}
const users = [
  {name: 'kim', age: 10, addr:'kor'},
  {name: 'joe', age: 20, addr:'usa'},
  {name: 'miko', age: 30, addr:'jp'}
];

users.map((user) => {
  renderUser(user);
});
// kim
// 10
// kor
// joe
// 20
// usa
// miko
// 30
// jp

users 배열의 map 메서드로 인하여 renderUser 함수에 users의 객체가 각각 전달된다. 각 객체의 key 값이 renderUser함수의 파라미터 받는 부분에서 비구조화 할당을 받았기 때문에 함수 내에서 객체의 key 값을 각각 가져올 수 있게 된다.

const users = [
  {name: 'kim', age: 10, addr:'kor'},
  {name: 'joe', age: 20, addr:'usa'},
  {name: 'miko', age: 30, addr:'jp'}
];

users.map(({name, age, addr}) => {
  console.log(name);
  console.log(age);
  console.log(addr);
});

마찬가지로 위처럼 map 메서드의 파라미터에도 바로 사용할 수 있다.

for of 문을 이용한 비구조화 할당

배열 내 객체들은 for of 문을 사용하여 비구조화 할 수 있다.

const users = [
  {name: 'kim', age: 10, addr:'kor'},
  {name: 'joe', age: 20, addr:'usa'},
  {name: 'miko', age: 30, addr:'jp'}
];

for(let {name : n, age : a} of users){
  console.log(n);
  console.log(a);
}

중첩된 객체 및 배열의 비구조화

중첩된 객체 및 배열 역시 비구조화가 가능하다.

const kim = {
  name: 'kim',
  age: 10,
  addr: 'kor',
  friends: [
    {name: 'joe', age: 20, addr:'usa'},
    {name: 'miko', age: 30, addr:'jp'}
  ]
};

let { name: userName, friends: [ ,{ name: jpFriend }] } = kim;
console.log(userName); // kim
console.log(jpFriend); // miko

References

자바스크립트 {…} […] 문법 (비구조화 할당/구조분해 할당)
JavaScript ) 비구조화 할당 알아보기
구조 분해 할당

updated_at 30-04-2020