책
모던 리액트 Deep Dive
를 읽으며 정리한 내용입니다.
ECMAScript
자바스크립트도 다른 언어들과 마찬가지로 매년 새로운 버전과 새로운 기능들이 나옵니다. 이러한 자바스크립트의 표준을 ECMAScript라고 합니다. 코드를 작성할 때 작성하고자 하는 자바스크립트 문법이 어떤 ECMAScript 버전에서 만들어졌는지 파악하며 사용해야 합니다. 왜냐하면 모든 브라우저와 런타임이 모든 문법을 지원하는 것이 아니기 때문입니다. 또, 모든 사용자들이 최신 버전의 브라우저를 사용하는 것도 아닐 뿐더러 다양한 브라우저를 사용하기 때문에 이런 브라우저들에서 지원하는 문법을 염두에 두어야 합니다.
이런 사용자의 다양한 브라우저 환경, 최신 문법을 작성하고 싶은 개발자들의 욕구를 해결하기 위해 나온 것이 바벨입니다. 바벨은 최신 문법을 다양한 브라우저에서 실행가능하도록 코드를 트렌스파일합니다. 바벨이 어떤 식으로 코드를 트랜스파일하는지 이해하면 향후에 애플리케이션을 디버깅하는데 큰 도움이 될 것 입니다.
- 트랜스파일: 한 언어로 작성된 소스 코드를 비슷한 수준의 추상화를 가진 다른 언어로 변환하는 것
구조 분해 할당(Destructuring assignment)
- 배열 또는 객체의 값을 분해해 개별 변수에 할당하는 것을 의미
- 주로 어떤 객체나 배열에서 선언문 없이 즉시 분해해 변수를 선언하고 할당하고 싶을 때 사용
- 배열 구조 분해 할당은 ES6에 처음 등장
- 객체 구조 분해 할당은 ECMA 2018에 처음 등장
배열 구조 분해 할당
const arr = [1, 2, 3, 4, 5]
const [first, second, third, ...arrayRest] = arr
// first = 1
// second = 2
// thrid = 3
// arrayRest = [4, 5]
-
흔히 사용하는 예시로는 React의 useState가 있다. 그런데 useState가 객체가 아닌 배열을 반환하는 이유는 무엇일까?
객체 구조 분해 할당은 사용자가 이름을 변경하여 사용하기가 번거롭다. 다만 배열 구조 분해 할당은 이름을 자유롭게 선언할 수 있기 때문에 배열로 반환하는 것이라 추측이 가능
const arr = [1,2,3,4,5]
const [first, , , , fifth] = arr
// first = 1
// fifth = 5
이런 식으로 중간 인덱스를 생략하고 필요한 인덱스에만 선언을 할 수도 있습니다. 다만 실수를 유발할 가능성이 크기 때문에 길이가 짧은 배열에서 자주 사용합니다.
const arr = [1,2]
const [a = 10, b = 10, c = 10] = arr
// a = 1
// b = 2
// c = 10
- 배열 분해 할당에는 기본값을 선언할 수도 있습니다.
- 이렇게 기본값으로 설정된 값은 해당 인덱스의 값이
undefined일
때만 사용됩니다. (0
,null
,''
일 때도 기본값이 사용되지 않음 무조건undefined
일 때만)
const arr = [1, 2, 3, 4]
const [first, ...rest] = arr
- 전개 연산자(spread operator)
- 특정 값 이후를 다시 배열로 선언하려고 할 때 사용
- 때문에 맨 뒤에서만 사용이 가능하다.
// 트랜스파일 전
const array = [1, 2, 3, 4, 5]
const [first, second, third, ...arrayRest] = array
// 트랜스파일 후
var array = [1, 2, 3, 4, 5]
var first = array[0],
second = array[1],
thrid = array[2],
arrayRest = array.slice(3)
객체 구조 분해 할당
- 객체에서 값을 꺼내온 뒤 할당하는 것을 의미
- 배열 구조 분해 할당과의 차이점은 객체 내부 이름으로 꺼내온다는 것
const obj = {
a: 1,
b: 2,
c: 3,
d: 4,
e: 5,
}
const { a, b, c, ...objRest } = obj
// a 1
// b 2
// c 3
// objRest { d: 4, e: 5 }
// 새로운 이름으로 할당하는 것도 가능
const { a: one, b: two } = obj
// one: 1
// two: 2
// 기본값을 주는 것도 가능하다
// 배열과 마찬가지로 undefined일 때 기본값이 할당된다
const { a = 10, b = 20, f = 30 } = obj
// a 1
// b 1
// f 30
단순히 값을 꺼내오는 것뿐만 아니라 변수에 있는 값으로 꺼내오는 방식도 가능하다.(계산된 속성 이름 방식)
const key = 'a'
const obj = {
a: 1,
b: 2,
}
const { [key]: a } = obj
// a = 1
게산된 속성 이름을 사용하려면 변수 네이밍: a
을 반드시 해주어야 한다. 그렇지 않으면 에러가 발생함
- 전개 연산자
꺼낸 값 이외의 나머지 값을 꺼내는 방식이기 때문에 배열과 마찬가지로 맨 뒤에서 사용해주어야 한다.
const obj = { a: 1, b: 1, c: 1, d: 1, } const { a, b, ...objRest } = obj // objRest { c: 1, d: 1 }
객체 구조 분해 할당은 배열의 구조 분해 할당에 비해 트랜스파일한 결과가 복잡하다. 즉, 객체 구조 분해 할당의 경우 트랜스파일을 거치면 번들링 크기가 상대적으로 크기 때문에 만약 웹 어플리케이션에서 ES5를 고려해야하고 객체 구조 분해 할당을 자주 사용하지 않는다면 사용에 있어서 고민이 필요하다.
...rest 같은 연산자 함수가 필요하다면 객체에서 특정 프로퍼티를 제외할 때 사용하는 메서드로 ladash.omit, rambda.omit 같은 외부 라이브러리를 사용하는 것도 대안이 될 수 있다.
전개 구문(Spread Syntax)
- 배열이나 객체, 문자열과 같이 순회할 수 있는 값에 대해 말 그대로 전개해 간결하게 사용할 수 있는 구문
- 배열 전개 구문 ES6
- 객체 전개 구문 ESMA2018
배열의 전개 구문
과거에는 배열 간에 합성을 하려면 push
, concat
, splice
등의 메서드를 사용해야 했으나 지금은 전개 구문을 사용하여 쉽게 배열을 합성할 수 있다.
const arr1 = [1, 2]
const arr2 = [...arr1, 3, 4] // [1, 2, 3, 4]
const arr3 = [...arr2] // [1, 2, 3, 4]
arr2 === arr3 // false
...배열
로 배열을 전개하고 이를 배열 내부에서 활용할 수 있다.- 이 방식을 활용하면 얕은 복사로 배열을 복사하는 것도 가능하다.
객체의 전개 구문
- 배열의 전개 구문과 비슷하게 사용
- 배열과 마찬가지로 객체를 합성하는데 있어 편리하다
const obj1 = {
a: 1,
b: 2,
}
const obj2 = {
c: 3,
d: 4,
}
const obj3 = { ...obj1, ...obj2, } // { a: 1, b: 2, c: 3, d: 4 }
- 객체의 전개 구문에서는 순서를 유의해서 사용해야 한다. 순서에 따라 덮어씌여지는 값이 달라질 수 있다.
const obj1 = {
a: 1,
b: 2
}
const obj2 = {
...obj1,
a: 10,
} // { a: 10, b: 2 }
const obj3 = {
a: 10,
...obj1
} // { a: 1, b: 2 }
전개 구문 트랜스파일
- 배열
- 크게 특이한 점 없고 concat 메서드를 사용하는 방식으로 트랜스파일한다.
- 객체
- 트랜스파일 전과 후의 차이가 객체 분해 할당과 비슷한 결과를 보인다.
- 단순히 값을 복사하는 배열과는 다르게 객체의 경우에는 속성값, 설명자 확인, 심벌 체크 등으로 인해 트랜스파일된 코드가 커지는 것을 볼 수 있다. 때문에 사용할 때 주의할 필요가 있다.
객체 초기자(object shorthand assignment)
- ECMAScript 2015에 도입된 기능
- 객체를 선언할 때 객체에 넣고자하는 키와 값을 가지고 있는 변수의 이름이 동일하다면 해당 값을 간결하게 넣어줄 수 있는 기능
const a = 1
const b = 2
const obj = { a, b } // { a: 1, b: 2 }
// 트랜스파일 후
var a = 1
var b = 2
var obj = { a: a, b: b }
- 객체를 좀 더 간편하게 선언할 수 있기에 매우 유용하고 트랜스파일 이후에도 큰 부담이 없다.
Array 프로토타입의 메서드: map, filter, reduce, forEach
map
,filter
,reduce
: 기존의 배열을 건드리지 않고 새로운 값을 만들어내기 때문에 안전하게 사용할 수 있음- 모두 ES5부터 사용한 문법이기 때문에 트랜스파일이나 폴리필 없이도 부담없이 사용 가능
Array.prototype.map
- 인수로 전달받은 배열과 똑같은 길이의 새로운 배열을 반환하는 메서드
const arr = [1, 2, 3, 4, 5]
arr.map((item, idx) => item * 2) // [2, 4, 6, 8, 10]
// 리액트에서 주로 특정 배열을 기반으로 리액트 요소를 반환할 때 많이 사용
const Elements = arr.map((item) => <Fragment key={item}>{item}</Fragment>)
Array.prototype.filter
map
과 동일하게 콜백함수를 인수로 받는데, 콜백함수의 결과가 truthy 조건에 만족하는 경우에만 해당 원소를 반환한다. 때문에 콜백의 결과에 따라 원본 배열의 길이 이하의 길이의 배열이 반환된다.
const arr = [1, 2, 3, 4, 5]
const evenArr = arr.filter((item) => item % 2 === 0) // [2, 4]
Array.prototype.reduce
- 초깃값에 누적한 결과를 반환하는 메서드로 콜백함수와 초깃값을 인수로 받는다.
- 초깃값에 따라 배열, 객체, 숫자 등을 반환할 수 있다.
const arr = [1, 2, 3, 4, 5]
const sum = arr.reduce((result, item) => result + item, 0)
map
,filter
에 비해 사용법이 복잡하고 직관적이지 않아 짧은 코드라면map
,filter
를 활용하여 구현하는 것도 것도 나쁘지 않다.
Array.prototype.forEach
- 콜백 함수를 받아 배열을 순화하면서 단순히 콜백을 실행하기만 하는 메서드이다.
- 아무런 반환값이 없다.
- 에러를 던지거나 프로세스를 종료하지 않는 이상 실행을 중간에 멈출 수 없다. 무조건 O(n)만큼 실행되기 때문에 코드 작성과 실행 시에 반드시 최적화할 가능성이 있는지 검토해야 한다.
const arr = [1, 2, 3]
arr.forEach(item => console.log(item))
삼한 조건 연산자
- 자바스크립트에서 유일하게 3개의 피연산자를 취할 수 있는 문법
const value = 10
const result = value % 2 == 0 ? '짝수' : '홀수'
조건문 ? 참일 때 : 거짓일 때
의 형태로 사용한다.- if 조건문을 간편하게 사용할 수 있어 JSX 내부에서 조건부로 랜더링하기 위해 자주 쓰이는 방법이다.
- 사용하다보면 중첩해서 사용하게 되는 경우가 있는데 그럼 가독성이 떨어지고 예측이 힘들어지기 때문에 가급적 중첩해서 사용하지 않는 것이 좋다.
정리
- 매년 자바스크립트는 새로운 문법이 나오는데 https://github.com/tc39/proposals에 방문하면 여러 제안들을 확인할 수 있다.
- 만약 최신 문법을 리액트에 반영하기로 맘을 먹었다면 바벨과 같은 도구를 이용한 트랜스파일을 지원하는지, 혹은 사용자의 디바이스에서 별도 조치 없이 사용가능한지를 꾸준히 점검한다면 안정적인 리액트 애플리케이션을 만드는데 큰 도움이 될 것이다.