[모던 리액트 Deep Dive] 01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트

H Kim·2024년 2월 18일
0

기술 책 읽기

목록 보기
12/20
post-thumbnail
post-custom-banner

1.1 자바스크립트의 동등 비교


1.1.1 자바스크립트의 데이터 타입

  • undefined : 선언됐지만 할당되지 않은 값
  • null : 명시적으로 비어 있음을 나타내는 값

  • falsy가 가능한 값의 실제 타입
타입설명
falseBooleanfalse는 대표적인 falsy한 값이다.
0, -0, 0n, 0x0nNumber, BigInt0은 부호나 소수점 유뮤에 상관없이 falsy한 값이다.
NaNNumberNumber가 아니라는 것을 뜻하는 NaN(Not a Number)은 falsy한 값이다.
'', "", ``String문자열이 falsy하기 위해서는 반드시 공백이 없는 빈 문자열이어야 한다.
nullnullnull은 falsy한 값이다.
undefinedundefinedundefined는 falsy한 값이다.

  • String
    자바스크립트 문자열의 특징 중 하나는 문자열이 원시 타입이며 변경 불가능하다는 것이다. 이것은 한번 문자열이 생성되면 그 문자열을 변경할 수 없음을 의미한다.

1.1.2 값을 저장하는 방식의 차이

  • 객체는 값을 저장하는 게 아니라 참조를 저장하기 때문에 앞서 동일하게 선언했던 객체라 하더라도 저장하는 순간 다른 참조를 바라보기 때문에 false를 반환하게 된다.

  • 따라서 자바스크립트 개발자는 항상 객체 간에 비교가 발생하면, 이 객체 간의 비교는 우리가 이해하는 내부의 값이 같다 하더라도 결과는 대부분 true가 아닐 수 있다는 것을 인지해야 한다.



1.2 함수


1.2.2 함수를 정의하는 4가지 방법

  • 함수를 자유롭게 선언하고 어디서든 자유롭게 호출하고 싶거나, 변수 선언과 다르게 명시적으로 함수를 구별하고 싶을 때는 함수 선언문이 더 좋을 수 있다. 함수 선언문은 함수가 선언된 위치에 상관없이 함수 호이스팅의 특징을 살리면 어디서든 호출할 수 있고, 또 변수 선언과 뚜렷하게 구별되는 차이점이 있다. 그러나 함수가 선언되기 전에 함수가 호출되는 것이 이상하게 느껴지는 사람도 있을 것이다. 함수 호출은 제일 먼저 보이고, 그다음에 실제 함수를 어디서 선언했는지는 해당 스코프를 끝까지 확인하지 않으면 개발자가 찾기 어렵다. 이는 관맇야 하는 스코프가 길어질 경우 특히 더 나쁘게 작용할 수 있다.

1.2.3 다양한 함수 살펴보기

(function (a, b) {
  return a + b
})(10, 24); // 34

((a, b) => {
  return a + b
})(10, 24) // 34
  • 이러한 즉시 실행 함수의 특성을 활용하면 글로벌 스코프를 오염시키지 않는 독립적인 함수 스코프를 운용할 수 있다는 장점을 얻을 수 있다. 함수의 선언과 실행이 바로 그 자리에서 끝나기 때문에 즉시 실행 함수 내부에 있는 값은 그 함수 내부가 아니고서는 접근이 불가능하기 때문이다.

  • 앞서 언급했듯이 자바스크립트의 함수가 일급 객체라는 특징을 화용하면 함수를 인수로 받거나 결과로 새로운 함수를 반환시킬 수 있다. 이런 역할을 하는 함수를 고차 함수(Higher Order Function)라고 한다.

1.2.4 함수를 만들 때 주의해야 할 사항

  • 함수의 부수 효과(side-effect)란 함수 내의 작동으로 인해 함수가 아닌 함수 외부에 영향을 끼치는 것을 의미한다. 이러한 부수 효과가 없는 함수를 순수 함수라 하고, 부수 효과가 존재하는 함수를 비순수 함수라고 한다.

  • 그렇다면 어떻게서든 항상 순수 함수로만 작성해야 할까? 그렇지 않다. 웹 애플리케이션을 만드는 과정에서 부수 효과는 어떻게 보면 피할 수 없는 요소다. 컴포넌트 내부에서 API를 호출한다면 어떨까? 외부에 어떠한 영향(HTTP request)을 끼쳤으므로 부수 효과다. console.log 또한 브라우저의 콘솔 창이라는 외부에 영향을 미쳤으므로 부수 효과다. HTML 문서의 title을 바꿨다면 이 또한 외부에 영향을 미쳤으므로 부수 효과다. 이러한 부수 효과는 웹 애플리케이션 개발에 있어 피할 수 없는 요소 중 하나다. 부수 효과를 만드는 것은 애플리케이션을 만들면서 피할 수 없는 요소지만 이러한 부수 효과를 최대한 억제할 수 있는 방향으로 함수를 설계해야 한다. 리액트의 관점에서 본다면 부수 효과를 처리하는 훅인 useEffect의 작동을 최소화하는 것이 그 일환이라 할 수 있다. useEffect의 사용은 피할 수 없지만 최소한으로 줄임으로써 함수의 역할을 좁히고, 버그를 줄이며, 컴포넌트의 안정성을 높일 수 있다.

  • 자바스크립트 개발자들이 프로젝트를 만들 때 사용하는 ESLint에는 max-lines-per-function이라는 규칙이 있다. 여기에 있는 표현을 빌리자면, 함수당 코드의 길이가 길어질수록 코드 냄새(문제를 일으킬 여지가 있는 코드)가 날 확률이 커지고, 내부에서 무슨 일이 일어나는지 추적하기 어려워진다. 이 규칙에서는 기본값으로 50줄 이상이 넘어가면 과도하게 커진 함수로 분류하고 경고 메시지를 출력한다. 그 외에도 중첩이 얼마나 많이 있고 콜백은 얼마나 많은지도 이 규칙에서 확인이 가능하다. 이 규칙의 요점은 간단하다. 하나의 함수에서 너무나 많은 일을 하지 않게 하는 것이다. 유닉스의 선구자인 더글러스 매킬로이(Malcolm Douglas McIlroy)가 말한 것처럼, 함수는 하나의 일을, 그 하나만 잘 하면 된다(Do One Thing and Do It Well). 그것이 함수의 원래 목적인 재사용성을 높일 수 있는 방법이다.

  • 또한 리액트에서 사용하는 useEffect나 useCallback 등의 훅에 넘겨주는 콜백 함수에 네이밍을 붙여준다면 가독성에 도움이 된다.

useEffect(function apiRequest() {
  // ... do something
}, [])


1.3 클래스


1.4 클로저

1.4.2 변수의 유효 범위, 스코프

  • 다른 언어와 달리 자바스크립트는 기볼적으로 함수 레벨 스코프를 따른다. 즉, {} 블록이 스코프 범위를 결정하지 않는다.
if (true) {
  var global = 'global scope'
}

console.log(global) // 'global scope'
console.log(global === window.global) // true
function hello() {
  var local = 'local variable'
  console.log(local) // local variable
}

hello()
console.log(local) // Uncaught RefferenceError: local is not defined

1.4.4 주의할 점

  • 클로저의 개념, 즉 외부 함수를 기억하고 이를 내부 함수에서 가져다 쓰는 메커니즘은 성능에 영향을 미친다. 클로저에 꼭 필요한 작업만 남겨두지 않는다면 메모리를 불필요하게 잡아먹는 결과를 야기할 수 있고, 마찬가지로 클로저 사용을 적절한 스코프로 가둬두지 않는다면 성능에 악영향을 미친다. 클로저는 공짜가 아니므로 클로저를 사용할 때는 주의가 필요하다.


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

1.5.2 이벤트 루프란?

  • 이벤트 루프의 역할은 호출 스택에 실행 중인 코드가 있는지, 그리고 태스크 큐에 대기 중인 함수가 있는지 반복해서 확인하는 역할을 한다. 호출 스택이 비었다면 태스크 큐에 대기 중인 작업이 있는지 확인하고, 이 작업을 실행 가능한 오래된 것부터 순차적으로 꺼내와서 실행하게 된다. 이 작업 또한 마찬가지로 태스크 큐가 빌 때까지 이루어진다.

  • 그렇다면 마지막으로 궁금해지는 것은 저 비동기 함수는 누가 수행하느냐다. n초 뒤에 setTimeout을 요청하는 작업은 누가 처리할까? fetch를 기반으로 실행되는 네트워크 요청은 누가 보내고 응답을 받을 것인가? 이러한 작업들은 모두 자바스크립트 코드가 동기식으로 실행되는 메인 스레드가 아닌 태스크 큐가 할당되는 별도의 스레드에서 수행된다. 이 별도의 스레드에서 태스크 큐에 작업을 할당해 처리하는 것은 브라우저나 Node.js의 역할이다. 즉, 자바스크립트 코드 실행은 싱글 스레드에서 이루어지지만 이러한 외부 Web API 등은 모두 자바스크립트 코드 외부에서 실행되고 콜백이 태스크 큐로 들어가는 것이다. 이벤트 루프는 호출 스택이 비고, 콜백이 실행 가능한 때가 오면 이것을 꺼내서 수행하는 역할을 하는 것이다. 만약 이러한 작업들도 모두 자바스크립트 코드가 실행되는 메인 스레드에서만 이루어진다면 절대로 비동기 작업을 수행할 수 없을 것이다.



1.6 리액트에서 자주 사용하는 자바스크립트 문법

  • 다른 언어와 마찬가지로 자바스크립트도 매년 새로운 버전과 함께 새로운 기능이 나온다. 이러한 자바스크립트 표준을 ECMAScript라고 하는데, 작성하고자 하는 자바스크립트 문법이 어느 ECMAScript 버전에서 만들어졌는지도 파악해야 한다. 왜냐하면 모든 브라우저와 자바스크립틑 런타임이 항상 새로운 자바스크립트 문법을 지원하는 것이 아니기 때문이다. 자바스크립트 개발자를 오랜 시간 괴롭혀 온 인터넷 익스플로러를 예로 들어보자. 인터넷 익스플로러 11은 ECMAScript 5(ES5)까지만 지원하기 때문에 최신 자바스크립트 문법을 사용할 수 없다. 만약 서비스하는 웹페이지가 인터넷 익스플로러 11도 지원해야 한다면 코드에서 최신 문법을 제공할 수 없다는 점을 고려해야 한다. 그리고 웹페이지에 접근하는 사용자의 브러우저와 버전은 개발자와 다르게 항상 최신 버전이 아니고, 크롬, 사파리, 파이어폭스 등 다양하기 때문에 이러한 다양한 브로우저에서의 문법 지원 또한 염두에 두어야 한다.

  • 이러한 사용자의 다양한 브라우저 환경, 그리고 최신 문법을 작성하고 싶은 개발자의 요구를 해결하기 위해 탄생한 것이 바로 바벨이다. 바벨은 자바스크립트의 최신 문법을 다양한 브라우저에서도 일관적으로 지원할 수 있도록 코드를 트랜스파일한다. 바벨이 어떻게 최신 코드를 트랜스파일하는지, 그리고 그 결과 어떤 코드가 생성되는지 이해하면 향후 애플리케이션을 디버깅하는 데 도움이 된다.


1.6.4 Array 프로토타입의 메서드: map, filter, reduce, forEach

  • Array.prototype.forEach
    콜백 함수를 받아 배열을 순회하면서 단순히 그 콜백 함수를 실행하기만 하는 메서드다.

  • forEach는 아무런 반환값이 없다. 단순히 콜백 함수를 실행할 뿐, map과 같이 결과를 반환하는 작업은 수행하지 않는다. 즉, 콜백 함수 내부에서 아무리 반환해도 모두 의미없는 값이 된다. forEach의 반환값은 undefined로 의미 없다는 것을 알아두어야 한다.

  • 또 한 가지 주의할 점은 forEach는 실행되는 순간 에러를 던지거나 프로세스를 종료하지 않는 이상 이를 멈출 수 없다는 것이다. break, return 그 무엇을 이용해도 배열 순회를 멈출 수 없다.

  • 중간에 return이 존재해 함수 실행이 끝났음에도 불구하고 계속해서 forEach 콜백이 실행되는 것을 볼 수 있다. 이는 return이 함수의 return이 아닌 콜백 함수의 return으로 간주되기 때문이다. 따라서 forEach를 사용할 때는 절대로 중간에 순회를 멈출 수 없다는 사실을 인지하고 있어야 한다. forEach 내부의 콜백함수는 무조건 0(n)만큼 실행되므로 코드 작성과 실행 시에 반드시 최적화할 가능성이 있는지 검토해 보자.


1.6.5 삼항 조건 연산자

const value = useMemo(
  () => (condition1 ? '1' : conditiom2 ? '2' : condition3 ? '3' : 'else'),
  [condition1, condition2, condition3],
)
  • 위 코드를 살펴보면 useMemo 조건에 따라 총 4개의 값을 반환하는 것을 알 수 있는데, 아무리 삼항 조건 연산자를 자주 써왔다 하더라도 이 연산의 결과를 쉽게 예측하기란 어렵다. 따라서 삼항 연산자는 가급적이면 중첩해서 쓰지 않는 편이 좋다.

  • 이러한 최신 문법을 리액트에 반영하기로 마음먹엇다면 이러한 코드를 사용할 준비는 돼 있는지, 즉 바벨과 같은 도구를 이용한 트랜스파일을 지원하는지, 혹은 사용자의 디바이스에서 별도 조치 없이 사용 가능한지를 꾸준히 점검한다면 안정적인 리액트 애플리케이션을 만드는 데 큰 도움이 될 것이다.


1.7 선택이 아닌 필수, 타입스크립트

1.7.1 타입스크립트란?

  • 어디까지나 타입스크립트는 자바스크립트의 슈퍼셋일 뿐이지, 자바스크립트에서 불가능한 일은 타입스크립트에서도 마찬가지로 불가능하다. 타입스크립트로 작성된 파일(.ts, .tsx)은 결국 자바스크립트로 변환돼서 Node.js나 브라우저 같은 자바스크립트 런타임 환경에서 실행되는 것이 최종 목표이기 때문이다. 타입스크립트 플레이그라운드에서 타입스크립트 코드를 작성해 보면 타입스크립트 코드가 결국에 어떤 자바스크립트 코드로 변환되는지 확인할 수 있다.

1.7.2 리액트 코드를 효과적으로 작성하기 위한 타입스크립트 활용법

  • any 대신 unknown을 사용하자
  • top type인 unknown과 반대되는 bottom type으로 never가 있다. 이 never 타입은 unknown과 반대로, 어떠한 타입도 들어올 수 없음을 의미한다.
  • 코드상으로 불가능한 타입을 나타낼 때 never가 사용된다.

  • 제네릭(generic)은 함수나 클래스 내부에서 단일 타입이 아닌 다양한 타입에 대응할 수 있도록 도와주는 도구다. 제네릭을 사용하면 타입만 다른 비슷한 작업을 하는 컴포넌트를 단일 제네릭 컴포넌트로 선언해 간결하게 작성할 수 있다.
function getFirstAndLast<T>(list: T[]): [T, T] {
  return [list[0], list[list.length - 1]]
}

const [first, last] = getFirstAndLast([1, 2, 3, 4, 5])

first // number
last // number

const [first, last] = getFirstAndLast(['a', 'b', 'c', 'd', 'e'])

first // string
last // string
  • T라는 제네릭을 선언해, 이를 각각의 배열의 요소와 반환 값의 요소로 사용했다.
post-custom-banner

0개의 댓글