모던 React Deep Dive (ch 1)

·2023년 12월 10일
0

React Deep Dive

목록 보기
1/2

모던 리액트 Deep Dive -Chapter 1

리액트의 장점

  • 명시적인 상태 변경
    • 단방향 바인딩만 지원한다. 이는 프로젝트의 규모가 커질 수록 많은 상태변화에 따라 예측할 수 없는 상황을 예방할 수 있다.
    • 대표적인 양방향 바인딩을 지원하는 프레임워크는 Angular이다. 양방향 바인딩은 편리하지만, 규모가 커지면 예측할 수 없는 상황을 마주할 가능성이 크다는 단점이 있다.

한 번 짚고 넘어가면 좋을 내용들

  • 백틱(``)으로 표현하는 문자열은 작은 따옴표 ('')나 큰 따옴표("")와 차이가 있다.
  • 백틱을 사용해서 표현한 문자열은 "템플릿 리터럴"이라고 불리는데, 백틱으로 반환된 문자열은 개행문자를 취급한다. (\n, \r 같은..)
  • 만약 DB에서 개행문자가 포함된 문자열을 View에 띄우고 싶다면, html 태그 중 <pre></pre>라는 태그를 사용하자.
  • Symbol() 타입은 동일한 값을 넣어도, 다르게 인식한다.
    • const name = Symbol("Kim");
    • const name2 = Symbol("Kim");
    • 두 상수는 각각 다른값으로 취급된다. ( name !== name2)
  • Symbol 타입은 Symbol() 함수로만 생성이 가능하다.
  • 리액트에서는 동등비교를 Object.is로 진행하고 있다.
  • 즉시 실행 함수 (IIFE, Immediately Invoked Function Expression)
  • 함수 정의 시점에 즉시 실행되는 함수.
  • 외부에 노출되면 안되는 것들을 실행시켜야 할 때, 유용하다고 함.
  • 재사용되지 않는 함수.

클로저

  • 개요

    • 함수형 컴포넌트의 구조와 작동 방식, 훅의 원리, 의존성 배열 등 함수형 컴포넌트의 대부분의 기술이 모두 클로저에 의존중. 따라서 함수형 컴포넌트 작성을 위해서는 클로저에 대해 이해하는 것이 필수.
  • 정의

    • 함수와 함수가 선언된 어휘적 환경의 조합
      • What's 어휘적 환경?
      • 변수가 코드 내부에서 어디서 선언됐는지를 말하는 것.
      • 호출되는 방식에 따라 동적으로 변경되는 this와는 다르게, 코드가 작성된 순간에 정적으로 결정된다.
    • 클로저는 컴퓨팅 자원 비용이 드는 방식이기 때문에, 사용하기 전에 이러한 점을 인식하고 넘어가면 좋다.

이벤트 루프와 비동기 통신의 이해

  • 이전에는 프로그램을 실행하는 단위가 오직 '프로세스'; 뿐이었다.
    • 하나에 프로그램에는 하나의 프로세스만이 할당되므로, 많은 작업을 수행하기 어려웠는데, 따라서 탄생한 것이 바로 '스레드' 실행 단위다.
    • 하나의 프로세스에서는 여러 개의 스레드가 생성될 수 있고, 스레드끼리는 메모리를 공유할 수 있기 때문에 여러가지 작업을 동시에 수행할 수 있다.
    • 이로써 하나의 프로세스 내부에서 여러 개의 스레드를 활용하며 동시 다발적인 작업을 처리할 수 있게 되었다.
  • 호출 스택이 비어있는지 여부를 확인하는 것이 이벤트 루프이다.
      1. 이벤트 루프는 단순히 이벤트 루프만의 단일 스레드 내부에서 해당 호출 스택 내부에 수행해야 할 작업이 있는지 확인.
      1. 수행해야 할 코드가 있는지 확인.
    • ‼️ "코드를 실행하는 것"과 "호출 스택이 비어있는지 확인하는 것" 모두가 단일 스레드에서 일어난다.
      • 두 작업은 동시에 일어날 수 없음. 한 스레드에서 순차적으로 일어난다.

React에서 자주 사용되는 JS 문법

  1. 구조 분해 할당
  • 정의 : 배열 또는 객체의 값을 분해해 개별 변수에 즉시 할당하는 것을 의미.
    • 배열과 객체에서 사용하며, 어떠한 객체나 배열에서 선언문 없이 즉시 분해해 변수를 선언하고 할당하고 싶을 때 사용한다.
  1. 객체 구조 분해 할당
  • 정의 : 말 그대로 객체에서 값을 꺼내온 뒤 할당
    ⭐️ 배열 구조 분해 할당과는 달리 객체는 객체 내부 이름으로 꺼내온다.
const people = {
  "kim" : "minsu",
  "park" : "minsung",
  "lee" : "su"
}

const {"kim", "park", "lee"} = people; // ➡️ minsu, minsung, su
  1. 전개 구문
  • 정의 : 구조 분해 할당과는 다르게 배열이나 객체, 문자열과 같이 순회할 수 있는 값을 전개하여 간결하게 사용할 수 있는 구문이다.

배열의 전개구문

과거에는 배열 간에 합성을 하려면 push(), concat(), splice()등의 메서드를 사용해야 했으나, 전개 구문을 활용하면 매우 쉽게 배열을 합성할 수 있다.

const testArr = ['a','b'];
const testArr2 = [...testArr, 'c', 'd', 'e', 'f'] // (testArr에 전개구문을 사용) ['a','b']를 전개구문을 사용하여 다 불러옴.

이는 기존 배열에 영향을 미치지 않고 배열을 복사하는 방법이다.

const arr1 = ['a', 'b'];
const arr2 = [...arr1];

arr1 === arr2 // false

왜 false일까?

  • 참조값은 명시적으로 보이는 값이 같아도, 메모리 주소가 다르기 때문에 false를 반환한다.

객체의 전개구문

객체에서도 배열과 비슷하게 사용이 가능하다. 객체를 새로 만들 때 전개 구문을 사용할 수 있고, 마찬가지로 객체를 합성하는 데 있어 편리함을 준다.

const obj1 = {
  a : 1,
  b : 2
}

const obj2 = {
  c : 3,
  d : 4
}

const newObj = {...obj1, ...obj2}; // {"a" : 1, "b" : 2, "c" : 3, "d" : 4 }

중요 차이점 (배열 전개구문과)

  • 객체 전개 구문은 순서가 중요하다. 복사한 객체의 순서가 바뀌면 예상치 못한 값으로 덮어씌워진 채로 반환될 수 있기 때문이다.

Array 프로토타입의 메서드 (map, filter, reduce, forEach)

위 메스드들은 기존 배열의 값을 건드리지 않고 새로운 값을 만들어내기 때문에 기존 값이 변경될 염려 없이 안전하게 사용할 수 있다.

  1. map()
  • 인수로 전달받은 배열과 똑같은 길이의 새로운 배열을 반환하는 메서드다. 배열의 각 아이템을 순회하면서 각 아이템을 콜백으로 연산한 결과로 구성된 새로운 배열을 만들 수 있다.
const arr = [1,2,3,4,5];
const doubleArr = arr.map(item => item * 2) // [2,4,6,8,10]
  • React에서는 특정 배열을 기반으로 어떤 리액트 요소를 반환하고자 할 때 주로 사용된다.
    • 댓글 컴포넌트, Card 컴포넌트 등...
const arr = [1,2,3,4,5];
const CommentCard = arr.map((item. idx) => {
  return <li key={idx}>{item}</li>
})
  1. filter()'
  • 콜백 함수를 인수로 받는다. 이 콜백 함수에서 '참' 조건을 만족하는 경우에만 해당 원소를 반환하는 메서드다.
  • 필터링 역할의 메서드이며, filter 결과에 따라 원본 배열의 길이 이하의 새로운 배열이 반환된다. (이말은 즉, 원본 배열보다 더 많은 요소를 가진 배열이 나올 수 없음을 의미한다.)
  • map()과 다륵데 같은 크기의 배열이 나오지 않을 수 있다.
  • 주로 기존 배열에 대해 어떠한 조건을 만족하는 새로운 배열을 반환할 때 쓰인다.
const arr = [1,2,3,4,5];
const evenArr = arr.filter(item => item % 2 === 0);
  1. reduce()
  • 콜백 함수와 함께 초기값을 추가로 인수로 받는다.
  • 초기값에 따라 배열이나 객체, 혹은 그 외의 다른 무언가를 반환할 수 있는 메서드다.
  • reducer 콜백함수를 실행하고, 이를 초기값에 누적해 결과를 반환한다.
const arr = [1,2,3,4,5];
const sum = arr.reduce((result, item) => {
return result + item
}, 0) // 15
  • 초기값은 0으로 설정, result도 0으로 설정.
      1. 이후 arr의 1번째 값인 1부터 0 + 1로 연산 시작.
      1. 0 + 1의 연산 결과인 1은 이제 result 값이 되었음.
      1. arr의 2번째 값인 2를 연산 시작. (1 + 2)
      1. 해당 방식으로 모든 배열을 순회하며 값을 연산하여 15의 결과값을 출력.

filter와 map의 조합과 reduce를 사용한 배열 처리 비교

// 짝수만 100을 곱해 반환하는 함수 예제

const arr = [1,2,3,4,5];

// filter, map의 조합
const filterAndMapResult = arr.filter(item => item % 2 === 0).map(item => item * 100);

// reducer로 처리
const reducerResult = arr.reduce((result, item) => {
  if(item % 2 === 0) {
    result.push(item * 100)
  }
  return result
}, [])

filter와 map의 조합이 가독성은 좋으나, 같은 배열에 대해 2번 순회함으로 상황에 맞춰서 메서드를 사용하면 되겠다.

  1. forEach()
  • 콜백 함수를 받아 배열을 순회하면서 단순하게 콜백함수를 실행시킨다.
const arr = [1,2,3,4,5];

const result = arr.forEach(num => console.log("num : ", num)) // 1,2,3,4,5
  • 앞선 3개의 메서드와 달리 forEach()메서드는 주의가 필요한 메서드다.
      1. forEach()메서드는 아무런 반환값이 없다. 즉, 단순 콜백 함수를 실행할 뿐, map과 같이 결과를 반환하는 작업은 수행하지 않는다. 따라서 콜백함수 내부에서 아무리 반환해도 모두 의미 없는 값이 된다. (forEach의 반환값은 undefined)
      1. forEach()는 실행되는 순간 에러를 던지거나 프로세스를 종료하지 않는 이상 해당 작업을 멈출 수 없다. break, return 모두 말을 듣지 않는다.
      1. 무조건 O(n)만큼 실행되므로 코드 작성과 실행 시에 반드시 최적화할 가능성이 있는지 검토하기.

✅ 나의 생각

  • forEach를 사용해야 할 경우를 마주하지 못했기 때문에 잘 와닿지 않음. 굳이 써야하는 상황이 있다면 어떤 상황이 있을지 예시를 찾아볼 것

TypeScript

any대신 unknown을 사용하자

  • any를 사용한다면 TS를 사용하는 의미가 없다.

그럼 어떨때 any를 사용해야 하는가?
➡️ JS에서 TS로 마이그레이션 하는 과정 정도에서 any를 사용하는 것은 일부 허용해볼 수 있겠다.

unknown은 어떨때 사용해야하는가?
➡️ 아직 어떤 값이 들어와야 하는지 모르겠을 때, 적용해볼 수 있다.

사용 예시

function whatIdo(callback : unknown) {
  callback(); // 'callback' is of type 'unknown'
}

위 예시처럼 unknown type을 바로 적용한다고 해서 바로 사용할 수 없다.

function whatIdo(callback : unknown) {
  if(typeof callback === 'function') {
  callback();
  }

  throw new Error('callback은 함수여야 실행됩니다.');
}

typeof를 사용하여 unknown에 직접 접근하는 대신, unknown값이 내가 원하는 타입일 때만 의도대로 작동하도록 적용한 케이스다.

profile
- 배움에는 끝이 없다.

0개의 댓글