1.6 리액트에서 자주 사용하는 자바스크립트 문법
자바스크립트 문법을 이해한다면 리액트의 작동 방식 또한 이해할 수 있다.
🔖 항상 새로운 버전이 나오는 JS의 문법을 숙지하고, 사용자의 입장도 고려하여 개발해야한다.
👍 이를 위해 사용하는 것이 바로 트랜스파일러인 "바벨"이다.
1.6.1 구조 분해 할당
🔖 배열이나 객체의 값을 분해해 개별 변수에 바로 할당하는 것을 의미
🏷️ 배열 구조 분해 할당
- 배열의 구조를 분해하여 개별 변수에 할당하는 것
- ","을 이용하여 값을 결정
// 스프레드 연산자 활용 예시
const arr = [1, 2, 3, 4, 5];
const [first, second, third, ...arrayRest] = arr;
// 1 2 3 [4, 5]
// 중간 인덱스 할당 생략
const [first, , , , , fifth] = arr;
// 1 5
// undefined만 가능한 기본값 사용 (사용하는 배열이 더 짧은 경우)
const arr = [1, 2];
const [a = 10, b = 10, c = 10] = arr;
// 1 2 10
const arr = [undefined, null, 0, ""];
const [a = 1, b = 1, c = 1, d = 1] = arr;
// 1 null 0 ''
🏷️ 바벨을 이용한 트랜스파일링 코드 (ES5)
// 전
const arr = [1, 2, 3, 4, 5];
const [first, second, third, ...arrayRest] = arr;
// 후
var arr = [1, 2, 3, 4, 5];
var first = arr[0],
second = arr[1],
third = arr[2],
arrayRest = array.slice(3);
👍 구조 분해 할당을 통해 이런 여러 줄의 작업을 한줄로 간소화할 수 있다! (GOAT)
🏷️ 객체 구조 분해 할당
- 객체에서 값을 꺼내와 할당
- 객체 내부 이름으로 꺼내옴
// 스프레드 연산자 활용 예시
const obj = {
a: 1,
b: 2,
c: 3,
};
const { a, ...objRest } = obj;
// 1 {b: 2, c: 3}
// 새로운 이름으로 재할당 또한 가능
const { a: first, b: second } = obj;
// first 1 second 2
// 기본값 또한 지정 가능
const { a = 10, b = 10, c = 10, d = 10 } = obj;
// 1 2 3 10
// 추가로 계산된 속성 이름 방식 사용 가능
const key = "a";
const { [key]: a } = obj;
// a = 1
❗추가로 위와 같은 계산된 속성 사용 시 이름을 선언하는 :a와 같은 네이밍 필요
🏷️ 바벨을 이용한 트랜스파일링 코드 (ES5)
// 전
const obj = {
a: 1,
b: 2,
c: 3,
};
const { a, ...rest } = obj;
// 후 .. 너무 길다
❗ES5 버전의 로직 자체보다 트랜스파일링 시 번들링 크기가 커진다는 것에 주목하고, ES5를 고려하는 경우 이를 남용하면 안된다는 것을 기억해야한다.
1.6.2 전개 구문
🔖 배열이나 객체, 문자열과 같은 순회할 수 있는 값을 전개해 간결하게 사용하는 구문
🏷️ 배열의 전개 구문
push()
,concat()
,splice()
등을 대체 가능
const arr1 = ["a", "b"];
const arr2 = [...arr1, "c", "d", "e"];
// ['a', 'b', 'c', 'd', 'e']
// 내부 배열에서 활용 가능 (기존 배열에 영향 없이 배열 복사)
const arr1 = ["a", "b"];
const arr2 = [...arr1];
arr1 === arr2; // false
🏷️ 객체의 전개 구문
- 객체 전개 구문은 순서가 중요! (순서를 다르게 할 시 아예 다른 객체가 되어버림)
const obj1 = {
a: 1,
b: 2,
};
const obj2 = {
c: 1,
d: 2,
};
const newObj = { ...obj1, ...obj2 };
// {'a': 1, 'b': 2, 'c': 1, 'd': 2}
const aObj = {
...obj1,
c: 3,
};
const bObj = {
c: 3,
...obj1,
};
shallowEqual(aObj === bObj); // false
✅ 결론적으로 배열은 크게 상관이 없으나, "객체"의 경우 트랜스파일 시 번들링 파일의 크기가 기존과 비교해 매우 커지므로, 사용 시 주의할 필요가 있다.
1.6.3 객체 초기자
🔖 객체 선언 시, 객체에 넣을 키와 값을 가지고 있는 변수가 이미 존재한다면 해당 값을 변수를 통해 바로 넣어줄 수 있는 야무진 기술이다.
const a = 1;
const b = 2;
const obj = {
a,
b,
};
// obj = { a: 1, b: 2}
👍 객체를 좀 더 간편하게 설계할 수 있고, 트랜스파일 시에도 부담없다..!
1.6.4 Array 프로토타입의 메서드: map, filter, reduce, forEach
🔖 흔히 말하는 고차 함수, 기존 배열 값을 건드리지 않고 새로운 배열을 리턴한다. (forEach 제외) 또한 ES5부터 사용해서 트랜스파일 시에도 변함이 없다.
🏷️ Array.prototype.map
- 인수로 전달받은 배열과 똑같은 길이의 새 배열을 리턴한다.
- 리액트에서는 특정 데이터 배열을 받아 결과를 렌더링하는 용도로 사용한다.
const arr = [1, 2, 3, 4, 5];
const doubleArr = arr.map((item) => item * 2);
// [2, 4, 6, 8, 10]
🏷️ Array.prototype.filter
- 콜백 함수를 받아 이 함수가 truthy 조건을 만족해야 원소를 리턴한다.
const arr = [1, 2, 3, 4, 5];
const filteredArr = arr.filter((item) => item % 2 === 0);
// [2, 4]
🏷️ Array.prototype.reduce
- 콜백과 함께 받는 초기값을 통해, 배열이나 객체 혹은 다른 무언가를 반환한다.
- 콜백 함수 실행 후, 초기값에 이를 누적해 결과를 반환한다.
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((result, item) => {
return result + item;
}, 0);
// sum = 15
🏷️ Array.prototype.forEach
- 콜백 함수를 받아 순회하며 콜백을 실행한다.
- 반환값이 없으므로 사용 시 유의해야한다.
- 배열을 순회하는 동안은 무슨 수를 쓰더라도 멈출 수 없다. (8톤 트럭~)
const arr = [1, 2, 3, 4, 5];
const newArr = arr.forEach((item) => item + 1);
// undefined
1.6.5 삼항 조건 연산자
🔖 JS에서 유일하게 3개의 피연산자를 취할 수 있다.
조건문 ? 참일_때_값 : 거짓일_때_값
의 형태
const value = 10;
const result = value % 2 === 0 ? "짝수" : "홀수";
// 짝수
❗가급적이면 중첩해서 쓰는 것은 자제!
1.6.6 책 정리 + 주관적인 정리
🔖 책 정리
- 리액트를 작성할 때 주로 사용되는 문법들의 정리이다.
- 최신 코드를 확인하고 싶다면 ESMASCript를 찾아보는 것이 좋다.
- 항상 코드 사용의 준비를 생각하고 다양한 상황에 대비해야 한다.
🏷️ 주관적인 정리
- 평소에 자주 사용해오면서 이를 바벨을 통한 트랜스파일링 시의 코드로도 볼 생각은 못했었는데 이번에 새로이 접하게 되었다.
- forEach의 순회는 강제적이라는 사실도 처음 접하게 되어서 다음에 사용할 때 좀 더 다양한 상황을 고민해볼 듯 하다.
1.7 선택이 아닌 필수, 타입스크립트
익숙해지면 편해~ (any를 남발하며)
🔖 동적 언어인 JS를 컴파일 시 전부 타입을 체크할 수 있도록 하여 안전한 코드의 작성과 더불어 버그도 크게 줄일 수 있다.
1.7.1 타입스크립트란?
🔖 자바스크립트에 타입을 가미한 것이 바로 타입스크립트이다.
// 타입스크립트를 사용하지 않는 타입 체크 예시
function test(a, b) {
if (typeof a !== "number" || typeof b !== "number") {
throw new Error("a와 b 모두 숫자로!");
}
return a / b;
}
test("안녕", "하이"); // error 발생
- 위의 경우 typeof 키워드를 이용해 타입 체크를 하고 있긴 하나, 일일히 작성하기 너무 번거롭다.
// 킹갓 타입스크립트를 사용한다면?
function test(a: number, b: number) {
return a / b;
}
test("안녕", "하이");
- 위의 경우 컴파일 시 타입을 체크해주어 tsc 키워드로 트랜스파일링 시 에러가 발새한다.
🔖 하지만 어디까지나 JS의 슈퍼셋이므로, 만능은 아니다. (결국 트랜스파일링을 통해 JS코드로 변환되어 최종 실행되기 때문)
✅ 타입스크립트 이전에 정적 타입 체크 라이브러리인 Flow가 있었으나, 타입스크립트의 대중화 및 VSCode와의 연계를 통해 현재는 거의 볼 수 없다.
👍 왠만하면 타입스크립트를 사용하는 버릇을 들이길 권장한다.
1.7.2 리액트 코드를 효과적으로 작성하기 위한 타입스크립트 문법
🔖 타입스크립트의 타입 시스템을 적극 활용하여 버그를 타도하자.
🏷️ any
대신 unknown
을 사용하자.
- any를 사용하는 것은 사실상 타입스크립트를 사용하지 않는 것이나 다름없다.
// any 사용
function do(callback: any){
callback()
}
do() // 타입스크립트에서는 에러 발생X, 실행 시 에러 발생
// unknown 사용
function do(callback: unknown){
if(typeof callback === 'function'){
callback();
return
}
throw new Error('callback은 함수여야 한다.');
}
do() // unknown은 모든 타입의 상위이지만, 사용 시 조건문이나 as를 이용해 타입을 정의해야한다.
🏷️ 반대로 never
타입도 존재한다.
- string과 number를 모두 포함하는 타입의 경우 존재하지 않아 never가 선언된다.
- 클래스 컴포넌트 선언 시 props를 받아들이지 않는다는 의미로 사용 가능하다.
🏷️ 타입 가드를 적극 활용하자
- 타입 사용 시 타입을 최대한 좁히는데에 도움을 준다.
- instanceof, typeof, in
// instanceof
try{
const res = await fetch('...');
return await res.json();
} catch(e) {
// e는 unknown이므로, 타입 체크
if(e instanceof UnAuthorizedError){
...
}
if(e instanceof UnExpectedError){
...
}
}
// typeof
if(typeof callback === 'function'){
callback();
return
}
// in
if('type' in somthingType){
...
}
🏷️ 제네릭
- 함수나 클래스 내부에서 단일이 아닌 다양한 타입에 대응할 수 있도록 도와준다.
- 타입만 다르고 작업은 비슷한 컴포넌트를 단일 제네릭 컴포넌트로 선언 가능하다.
function getFirstAndLast<T>(list: T[]): [T, T] {
return [list[0], list[list.length - 1]];
}
const [first, last] = getFirstAndLast([1, 2, 3, 4, 5]);
const [first, last] = getFirstAndLast(["a", "b", "c", "d", "e"]);
// 제네릭을 선언하여 number와 string 둘다 가능하도록 했다.
👍 대표적으로 useState의 타입을 지정할 때 사용한다.
✅ 제네릭을 하나 이상 사용도 가능하지만 적절한 네이밍이 요구된다.
function multiGen<First, Last>(a1: First, a2: Last): [First, Last] {
return [a1, a2];
}
const [a, b] = multiGen<String, Boolean>("true", true);
// 제네릭 선언으로 에러없이 동작
🏷️ 인덱스 시그니처
- 객체의 키를 정의하는 방식
type Hello = {
[key: string]: string; // 인덱스 시그니처
};
const hello: Hello = {
hello: "hello",
hi: "hi",
};
hello["hi"]; // hi
hello["안녕"]; // undefined
❗유용하긴 하나 존재하지 않는 키로 접근 시 undefined 반환 가능성이 커진다. 따라서 객체의 키 타입의 범위를 좁혀야 한다.
// record
type Hello = Record<"hello" | "hi", string>;
// 타입을 이용한 인덱스 시그니처
type Hello = { [key in "hello" | "hi"]: string };
❗또한 인덱스 시그니처 사용 시 이슈를 마주할 수도 있다.
const hello: Hello = {
hello: "hello",
hi: "hi",
};
Object.keys(hello).map((key) => {
const value = hello[key];
return value;
});
- 위 코드의
Object.keys
는string의
배열을 반환한다. 따라서 hello의 인덱스 키로 접근이 불가능하다.
✅ 요런 식으로 해결 가능하다.
(Object.keys(hello) as Array<keyof Hello>).map((key) => {
const value = hello[key];
return value;
});
- 위 코드는 as 키워드를 이용해 사용자가 단언한 타입으로 설정하여 접근이 가능하도록 만들었다.
function keyOf<T extends Object>(obj: T): Array<keyof T> {
return Array.from(Object.keys(obj)) as Array<keyof T>;
}
keyOf(hello).map((key) => {
const value = hello[key];
return value;
});
- 위 코드는 keyOf라는 함수를 만들고 Object.keys를 대체하는 로직을 구현했다.
keyOf(hello).map((key) => {
const value = hello[key as keyof Hello];
return value;
});
- 또는 위 코드처럼 key 자체에 직접 as 키워드를 통해 타입 단언을 설정할 수도 있다.
🫨 왜 Object.keys
의 타입은 string[]
으로 강제되어 있을까..
- JS는 객체가 열려있는 구조로 만들어져 객체가 필요한 변수나 메서드만 지니고 있다면 이것이 해당 타입에 속하도록 인정해준다.
type Car = { name: string };
type Truck = Car & { power: number };
function horn(car: Car) {
console.log(`${car.name}: 운전 똑바로 안해??`);
}
const truck: Truck = {
name: "비싼차",
power: 100,
};
- 위 코드의
truck은
Car
에 필요한 속성을 다 가지므로,horn(truck)
을 실행해도 정상적으로 작동한다.
1.7.3 타입스크립트 전환 가이드
🔖 점진적으로 타입스크립트로 변환해 나간다면 훨씬 쉽게 마이그레이션 할 수 있다!
🏷️ ts.config.json 작성하기
- outDir : 트랜스파일 된 JS 코드를 담을 폴더를 결정한다.
- allowJS : .js 파일을 허용할 것인지 결정한다. (자바스크립트가 존재하므로 true)
- target : 생성되는 타입스크립트의 버전을 결정한다.
- include : 트랜스파일 할 자바 및 타입스크립트 코드를 지정한다.
🏷️ JSDoc과 @ts-check를 활용해 점진적으로 전환하기
- 타입스크립트로 전활할 필요 없이 타입 체크가 가능하다.
🏷️ 타입 기반 라이브러리 사용을 위해 @types 모듈 설치하기
- 리액트에서 타입스크립트를 사용하기 위해서도 이를 설치해야한다.
🏷️ 파일 단위로 조금씩 전환하기
- unknown과 같은 타입을 적극 활용하여 점진적으로 전환시켜 나간다.
- 화딱지가 나더라도 참으면서 하나씩 해보자
1.7.4 책 정리 + 주관적인 정리
🔖 책 정리
- 타입스크립트는 자바스크립트 생태계에서 점점 비중을 넓혀가고 있다.
- 적응만 된다면 자바스크립트는 기억도 안날 것이다.
- 하지만 자바스크립트의 기본적인 이해도가 결국 타입스크립트의 밑바탕이 되므로, 항상 이를 인지하며 꾸준히 공부해야 한다.
🏷️ 주관적인 정리
- 타입스크립트를 사용하는 이유가 단순히 타입 정의 때문만은 아니라는 것을 알 수 있었다.
- 새로운 객체 키를 정의하는 방식은 인덱스 시그니처에 대해 알 수 있었고, 관련하여 조금 더 찾아보고 고민하는 시간을 가질 수 있었다.
'Front-End Study > 모던 리액트 딥다이브 스터디' 카테고리의 다른 글
모던 리액트 딥다이브 - 7회차 [3-1, 3-2, 3-3] (2) | 2024.06.19 |
---|---|
모던 리액트 딥다이브 6회차 [2-3, 2-4, 2-5] (0) | 2024.06.17 |
모던 리액트 딥다이브 - 5회차 [2-1, 2-2] (1) | 2024.06.10 |
모던 리액트 딥다이브 - 3회차 [1-3, 1-4, 1-5] (1) | 2024.06.03 |
모던 리액트 딥다이브 - 2회차 [1-1, 1-2] (0) | 2024.06.01 |