구조분해 할당과 스프레드 연산자는 프론트 개발자라면 꼭 알고 가야하는 연산자 중 하나로 특히 React에서 많은 활용을 한다.
props를 내려줄때나 state 업데이트를 할 때나 많은 사용을 한다
그 전에 할당 연산자를 알아보면
우리가 변수를 선언한 후 값을 할당한다. 라는 얘기를 많이 들었을 것이다. 거기서 나온 것이 바로 할당이다
let x,y;
x = y = 9; // x = 9, y = 9
const z = (y++, x+y); z = 19
이와 같이 =
를 활용 해서 할당하는 것이 바로 할당 연산자이다.
const z
부분은 ( )
안의 y++
가 먼저 실행되어 y
가 1이 증가해서 10+9
가 돼서 19
가 된다.
뭔가 말이 이해된다면 이 연산을 쉽게 볼 수 있을 것이라 생각된다.
말 그대로 구조 자체를 분해해서 각각 할당하는 것인데
const u = {id: 1, name: 'lee', age: 29}
let {id, name, addr} = u
console.log(id,name,addr) // 1 , 'lee', 29
이처럼 객체나 배열등의 구조를 분해해서 각각 할당해주는 것인데 하나하나 파보면
선언된 u
라는 오브젝트가 있고 아래에 let
변수 선언문 뒤에 동일한 오브젝트 { }
를 만들었다.
이후 안에 변수로 선언될 녀석들을 적는데 이 변수 이름들은 u
라는 오브젝트의 키값과 동일하게 적는다.
이 변수 이름을 이용해 js가 u
오브젝트에서 해당 변수 이름을 키값으로 밸류 값을 찾아 해당 변수에 할당하게 된다.
이는 배열도 마찬가지인데
const arr = [1, 2, 3, 4, 5]
let [a,b,,,c] = arr
console.log(a,b,c) // 1,2,5
배열은 객체와 달리 키값이 눈에 보이지가 않는데 실제로는 배열도 객체로 각각 index: value
라는 프로퍼티들로 이루어져 있다
Reflect.ownkeys
를 사용해서 key
값을 뽑아내면 실제로 0부터 시작한 index값들이 붙어있고 끝에 length
라는 키값이 있는 걸 확인해 볼 수 있다.
그럼 구조분해 할당을 할 때 왜 [a,b,,,c]
와 같은 불필요한 ,
를 붙이는 것일까?
아까 오브젝트를 구조분해 할당 할 때 각 변수의 이름들을 이용해 키값으로 활용하여 밸류를 찾는다고 했는데 배열의 경우 배열에서 선언된 변수들의 위치 인덱스를 통해 할당할 arr
의 배열 인덱스의 위치와 동일하게 변수를 할당하게 된다
따라서 만약 [a,b,c]
로 선언하게 되면 1,2,3
이라는 값이 되게된다
그러니 항상 주의해서 작성하자
왜 JS의 배열 끝에는
length
라는 프로퍼티가 존재할까?
배열은 리스트와 같아서 트리 형태로 살펴본다면 각각의 인덱스 값에서 다음 값의 위치를 함께 가지고 있다.
0번째 인덱스를 찾으면 0번째 인덱스에서 1번째 인덱스로 내려오고 1번째 인덱스를 통해 2번째 인덱스로 내려오고 그런 방식이다 보니 중간에 하나가 빠지게 되거나 0번째 인덱스를 빼버리는 경우 배열 전체가 다시 설정되는 그런 불편함이 있다
그러다보니 보통length
라는 배열의 길이를 구하려면 0번째부터 마지막 인덱스까지 쭉 타고 들어가 총 배열 크기를 카운팅 해야 하는데 만약 우리가 자주 사용하는for(let i=0; i<arr.length;i++)
과 같이 반복문안에서 사용해 버리면 반복문이 실행할 때마다 그 긴 배열이 카운팅되어 제곱에 가까운 시간이 생기게된다.
그래서 실제로arr.length
를 다른 변수에 담아놓고for
문에서 사용하는 것이 카운팅 횟수를 줄일 수 있는 방법이다.
그렇기에 JS의 경우 배열이 만들어질 때 해당 배열의 크기를length
프로퍼티로 가지고 있게 하는 방법이 채택되었다고 볼수있다
좀 더 복잡한 분해 할당으로 들어가기 전 구조분해 할당 시 사용되는 간단한 연산자를 봐보자
const obj = {id:1,what:"lee"}
const {id,what:name,addr = 'seoul'} = obj
console.log(id,name,addr) // 1 , 'lee' , 'seoul'
이와 같이 :
는 what
을 통해 찾게된 value값을 내가 원하는 name
라는 이름의 변수로 할당할 수 있게 변경해준다
뒤에 선언된 addr
의 경우 obj
에 해당 프로퍼티가 없는 것을 볼 수 있다. 이렇게 될 경우 addr
은 undefined
로 값이 할당되게 되는데 이는 매우 좋지 않으므로 =
할당 연사자를 붙여 기본값을 설정해주는 것이다.
함수에서 파라미터 값을 =
을 통해 기본값을 정해주는 것과 동일한 방법이다.
그럼 좀 더 복잡한 destructuring을 알아보자
아래 코드들은 연속적으로 작성된 하나의 프로세스 안에서 실행된 코드들이라고 생각하며 살펴보자
const user = {name:'Lee', age: 30}
const fn = ({age}) => age
console.log(fn(user)) // 30
한수안의 arguments로 해당 객체를 보내면서 도착했을 때 파라미터를 destructuring하여 age
만 뽑아낸것이다.
이렇게 할 경우 파라미터를 객체로 받아 user.age
와 같은 불필요한 코드를 짧게 줄일 수 있다.
이 방법은 React 프레임 워크를 사용할 때 많이 사용되니 꼭 알아두자
const {age2:age3 = fn(user)} = {age22: 20};
console.log(age3)//30
console.log(age2)// not defined
이는 구조분해 할당시 age2
의 키값이 없으니 age2
를 찾지 못했고 :
를 통해 변수 이름은 age3
로 변해서 age2
는 not defiend
라는 에러가 발생한 것
하지만 age3
는 =
연산자를 이용해 fn
함수가 내뱉은 값이 할당되니 30
이 출력되는 것이다
혹시 중첩되는 객체를 구조분해할당 하려고 하면
const u3 = {id:3, name: 'kim', addr: {id: 1, city: 'Seoul'}};
let {id:idd, addr:{id:aid}} = u3; // idd =3, aid = 1
똑같이 구조분해 할당을 하는데 addr
을 통해 1번째 프로퍼티를 찾고 해당 프로퍼티 안을 {}
구조분해 할당을 한번더 해서 그 안의 id
를 찾게 되는 것이다.
여기서 사용되는 :
는 이름 변경의 연산자가 아님을 꼭 기억하자
객체와 배열에서 사용할 수 있는 연산자들이 있다
[]
를 사용할 경우 키값을 적을 때 변수를 사용할 수 있다.[]
를 사용할 때 대괄호 안에 키값을 string으로 주고 싶다면 꼭 ''
을 사용하자u.hasOwnProperty('id') <==> Reflect.has(u,'id')
const d = new Dog()
class를 사용할 때 많이 봤을 것 같다d instanceof Dog
d라는 객체가 Dog라는 오브젝트의 프로토타입 체인에 걸리는지 확인한다...
연산자를 통해 나머지 값들을 다 붙일 수 있다rest(
...
)연산자는 spread와 동일하게 사요하지만 동작은 다르다.
spread를 더 쉽게 이해하고 싶다면arr
이란[1,2,3,4,5]
라는 배열이 존재할 경우...arr
을 사용한다면[]
이 벗겨진다고 생각하자.
만약 distructuring과 함께 사용한다면 rest로 동작하게 될텐데const [a,b,...c] = arr
을 사용한다면a
와b
는 각 인덱스 값들이 들어가지만c
는 나머지[3,4,5]
인 나머지 값들이 들어가게 된다.
객체에서 사용할 경우 미리 spread 연산자로 복사시킨후 해당 키값을 다른 값으로 다시 적으면 위에 덮어쓸 수 있게 된다
user객체를 id와 name을 출력하는 3개의 함수로 만들어보자
const hong = {id:1, name:'hong'}
const f1 = (obj) => {
console.log(`id:${obj.id},name:${obj.name}`)
}
const f2 = ({id,name}) => {
console.log(`id:${id},name:${name}`)
}
const f3 = () => {
console.log(`id:${this.id},name:${this.name}`)
}
const f4 = (id,name) => {
console.log(`id:${id},name:${name}`)
}
const f5 = (...args) => {
if(!args.length)return;
const {id,name} = args;
console.log(`id:${id},name:${name}`)
}
f1(hong)
f2(hong)
f3().bind(hong)()
f4(hong.id,hong.name)
f5(hong)