본문 바로가기
Front-End Study/모던 리액트 딥다이브 스터디

모던 리액트 딥다이브 - 4회차 [1-6, 1-7]

by 코딩기 2024. 6. 8.
728x90

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.keysstring의 배열을 반환한다. 따라서 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 책 정리 + 주관적인 정리


🔖 책 정리

  • 타입스크립트는 자바스크립트 생태계에서 점점 비중을 넓혀가고 있다.
  • 적응만 된다면 자바스크립트는 기억도 안날 것이다.
  • 하지만 자바스크립트의 기본적인 이해도가 결국 타입스크립트의 밑바탕이 되므로, 항상 이를 인지하며 꾸준히 공부해야 한다.

🏷️ 주관적인 정리

  • 타입스크립트를 사용하는 이유가 단순히 타입 정의 때문만은 아니라는 것을 알 수 있었다.
  • 새로운 객체 키를 정의하는 방식은 인덱스 시그니처에 대해 알 수 있었고, 관련하여 조금 더 찾아보고 고민하는 시간을 가질 수 있었다.