본문 바로가기
개발/자바스크립트

[자바스크립트] 구조 분해 할당(destructuring assignment)에 대하여

by 핸디(Handy) 2021. 3. 7.

Let's study Destructuring Assignment in JavaScript

앞써 자바스크립트스러운 코드 스타일에서 비구조화 할당에 대해 알아봤습니다.

2020/11/20 - [개발/자바스크립트] - [자바스크립트]JS다운 코드 스타일 #4. 비구조화 할당

우리가 알다시피 객체와 배열은 가장 많이 쓰이는 자료구조입니다. 과장을 보태자면 이 2가지만 제대로 활용할 줄 알면 자바스크립트의 자료구조는 더 이상 필요 없다고 봐도 될 정도입니다.

그만큼 많이 쓰입니다. 특히 함수에 파라미터로 전달하는 경우 대부분이 객체, 배열, 또는 원시값입니다. 원시값의 경우 하나의 값이라 상관이 없지만 객체, 배열일 경우 불필요하게 모든 데이터들이 넘어갑니다.

물론 코드를 깔끔하거 어썸하게 구현한다면 대부분 상황에서 문제가 없습니다만 그래도 좀 더 나은 형태로 구현하고 싶은 게 개발자 욕심입니다.

이럴 때 구조 분해 할당 또는 비구조화 할당이라는 문법을 사용할 수 있습니다.

다시 말해서 객체, 배열에 저장된 데이터 전체가 아닌 일부만 필요할 때 객체, 배열을 변수로 분해할 수 있도록 하는 문법입니다.


기본적인 사용법은 코드로 살펴보겠습니다.

//배열 예시
let stockPriceList = [100, 200, 300, 400, 500];
let [amazon, tesla] = stockPriceList;
console.log(amazon); // 100
console.log(tesla); // 200

//객체 예시
let stock = {amazon: 100, tesla: 200};
let {amazon, tesla} = stock;
console.log(amazon); // 100
console.log(tesla); // 200

//중첩 객체 예시
let stock = { apple: { price: 100 } };
let {apple: {price}} = stock; //price : 100
console.log(price); // 100

아주 간단하게 좌변에 원하는 부분만, 우변에 전체 부분으로 가져옴을 확인할 수 있습니다.

//배열 예시
let stockPriceList = [100, 200, 300, 400, 500];
let amazon = stockPriceList[0]; // Ooooops!!
let tesla = stockPriceList[1]; // Ooooops!!

//객체 예시
let stock = {amazon: 100, tesla: 200};
let amazon = stock.amazon; // Ooooops!!
let tesla = stock.tesla; // Ooooops!!

따라서 위에 같이 할 필요가 없어지는 편리함이 생겼습니다.


그렇다면 분해된 대상 좌변과 분해할 대상 우변에는 어떤 것이 올 수 있을까요?

분해할 대상 우변 : 모든 이터러블(iterable, 반복 가능한 객체)이  올 수 있습니다.

// string
let [a,b,c] = "ABC" // a : A, b : B, c : C

// set
let [a,b,c] = new Set([A,B,C]) // a : A, b : B, c : C

// object
let [a,b,c] = { a:A, b:B, c:C };
//Uncaught TypeError: {(intermediate value)(intermediate value)(intermediate value)} is not iterable at <anonymous>:1:13

그래서 배열에 일반 객체를 넣을 경우 에러가 뜨는 것을 확인할 수 있습니다. 

하지만 만약 일반 객체가 아닌 iterable 객체가 들어오면 어떻게 될까요?

let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  console.log(num) // 1, 2, 3, 4, 5
}

let [a,b,c,d,e,f] = range; // 1,2,3,4,5,undefined

 

제대로 동작하는 것을 볼 수 있습니다. 이렇든 이터러블이 된다면 분해될 대상이라고 보고 있습니다.

이 외에 split등의 문법이 올 수 있습니다. split의 반환값이 배열이니 너무도 당연한 얘기이긴 합니다.

let stock = { amazon : 100, tesla : 200};
[stock.amazon, stock.tesla] = "200 400".split(" "); // { amazon : 200, tesla : 400};

분해된 대상 좌변 : 할당할 수 있는(assignables)’ 모든 것이 올 수 있습니다.

위의 코드를 다시 보겠습니다.

let stock = { amazon : 100, tesla : 200};
[stock.amazon, stock.tesla] = "200 400".split(" "); // { amazon : 200, tesla : 400};

보시다시피 객체의 property도 되고 당연히 일반 변수 또한 가능합니다.


이제 기본적으로 어떤 것이 분해될 대상이 되고 분해된 결과가 되는지 확인했으니 추가적인 팁을 알아보도록 하겠습니다.

#TIP 1 : 분리하여 분해하기

let [one, ...theOther] = [1, 2, 3, 4, 5];
// one : 1, theOther : [2,3,4,5]

... 연산자(spread) 를 이용해서 하나와 나머지로 분해 할당할 수 있습니다.

#TIP 2 : 기본값을 가져오면서 분해하기

let [a, b, c]] = ["A","B"];
// a = A, b = B, c = undefined

let [a ="A", b ="B", c ="C"] = ["A","B"];
// a = A, b = B, c = C

보시다시피 위에 코드는 c의 값이 없지만 기본값을 설정해주면 undefined 일 경우 기본값으로 설정한 값을 가져옵니다.

추가적으로 만약 falsy 값이면 어떻게 되는지는 다음과 넣고 확인해보겠습니다.

let [a ="A", b ="B", c ="C"] = ["A","B", false];
// c : false

c의 기본값은 C 대신에 false가 제대로 들어갔음을 확인 할 수 있습니다.

#TIP 3 : 다른 이름으로 분해하기

let {width : w, height : h}= { width : 10, height : 20 };
// w : 10, width : Uncaught ReferenceError: width is not defined

 width를 w로 받아올 수 있습니다. 또한 당연히 width는 에러가 뜹니다.

추가적으로 다른 이름으로 분해하는 동시에 기본값을 설정할 수 있습니다.

let {width : w, height : h = 20}= { width : 10 };
// w : 10, h :20

// 다만 순서를 바꾸면 안됩니다.
let {width : w, height = 20 : h}= { width : 10 };
// Uncaught SyntaxError: Unexpected token ':'

보면 뭔가 타입스크립트의 변수 선언 느낌이네요.

갑자기 타입스크립트로 구조 분해 할당할 때 기본값은 어떻게 가져올까 고민해봤습니다.

// example.ts
let stock = { apple : 100};
let { apple =10 , tesla = 10} : {apple : number, tesla? : number}= stock;
console.log(apple, tesla) // 100, 10

tesla의 타입에 ?를 해준 이유는 타입스크립트 정적분석기가 알아서 없다고 빨간줄을 쳐주기 때문입니다.

타입스크립트에서도 기본값은 물론 다른 이름으로 받아올 수 있습니다.

// example.js
let stock = { apple : 100};
let { apple : a =10 , tesla = 10} : {apple : number, tesla? : number}= stock;
console.log(a, tesla) // 100, 10

 

#TIP 4 : 변수 선언 없이 분해하기

이번엔 실제 제가 사용하던 코드를 간략화하여 예시를 보겠습니다.

let title = "title";
let margin = undefined;
let info = {};


let makeInfo = ( title, margin ) => {
	({title, margin} = getDefaultInfo(title, margin)); // <- ***** 요기!!
    return `${title} with margin ${margin}`
}

let getDefaultInfo = (title, margin) => {
	if(title === undefined) title = "default title";
    if(margin === undefined) margin = 5;
	return { title, margin};
}

info = makeInfo(title, margin); // "title with margin 5"

코드는 title, margin의 정보를 가지고 ( 만약 없다면 기본값을 받아오고) 특정 문자열을 조합하는 로직입니다.

어차피 title, margin이 있으니 이걸 가지고 문자열을 만듭니다. 

하지만 만약 해당 문법이 없다면 이런 일이 일어났을 겁니다.

// 이미있다고 하거나
let makeInfo = ( title, margin ) => {
	let {title, margin} = getDefaultInfo(title, margin);
    return `${title} with margin ${margin}`
}
// Uncaught SyntaxError: Identifier 'title' has already been declared

//우회방안으로 
let makeInfo = ( inputTitle, inputMargin ) => {
	let {defaultTitle, deafultMargin} = getDefaultInfo(inputTitle, inputMargin);
    return `${defaultTitle} with margin ${deafultMargin}`
}

보시다시피 코드가 더러워지고 이상해집니다.

근데 ( ) 로 안 묶고 그냥 분해 할당하면 어떻게 되냐? 에러가 발생합니다.

let title, margin;
{title, margin} = {title: "test", margin: 5};
// Uncaught SyntaxError: Unexpected token '='

자바스크립트는 표현식 안에 있지 않으면서 코드 상에 있는 {...}를 코드 블록으로 인식합니다. 따라서 코드 블록 { title, margin}과 코드 블록 {title: "test", margin: 5} 이 있다고 여기는 겁니다. 근데 뜬금없이 코드 블록 사이에 = 이 있으니 에러를 내뱉는 것입니다.

따라서 이 부분을 표현식으로 만들기 위해 ( ) 로 감싸버리면 해결됩니다.

 

#TIP 5 : temp 없이 변수 교환하기

기존에 구조 분해 할당이 없었을때에는 두 값을 교환하려면 임시 변수가 필요했습니다. 하지만 이젠 필요없어졌습니다.

let a = 1;
let b = 3;

[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1

보시다시피 기존의 temp 또는 swap 같은 임시변수 없이 훨씬 직관적으로 교환을 할 수 있게 되었습니다.


이번 포스트에서는 구조 분해 할당에 대해서 기본 사용법부터 팁 5가지에 대해 알아보았습니다.

또한 별도로 타입스크립트에서도 똑같은 방식으로 사용가능함을 알아보았습니다.

구조 분해 할당 혹은 비구조화 할당이라고 불리는 이 문법은 사용하면 할수록 가치있는 문법이라고 생각합니다.(개인적으로) 

따라서 세종대왕님의 말씀을 빌리며 글을 마무리하겠습니다.

사람마다 하여금 쉬이 익혀 날마다 씀에 편안케 하고자 할 따름이다.
-훈민정음 서문-

 

<추가 자료>
developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment
ko.javascript.info/destructuring-assignment

댓글