JiSoo's Devlog

[모던 리액트 Deep Dive] 1장 본문

Frontend/React

[모던 리액트 Deep Dive] 1장

지숭숭숭 2024. 4. 8. 10:48

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

 

● 원시 타입 : 객체가 아닌 모든 타입으로 메서드를 갖지 않는다

undefined : 선언 후 값 할당 X

null : 명시적으로 비어있음을 나타내는 값, typeof로 확인 시 object 반환

Boolean: 참 거짓, 조건문에서 truthy, falsy 값 존재

  falsy → false, NaN, null, undefined, 0, -0, "", '', ``

  truthy → if (1){ }

Number : 모든 숫자, 진수별로 별도 데이터 타입 제공 X

BigInt : number의 한계를 넘은 더 큰 숫자 저장

String : 텍스트 타입(작은따옴표, 큰따옴표, 백틱)

  → 템플릿 리터럴은 같은 줄 바꿈 가능, 문자열 내부에 표현식 작성 가능

  → 문자열이 원시 타입이라 한 번 생성되면 변경 불가

Symbol : 중복되지 않는 어떠한 고유한 값, 심벌 함수를 이용해서만 생성 가능

 

객체 타입 : 자바스크립트를 이루는 대부분의 타입으로 배열, 함수, 정규식 등 포함

object

 

가장 큰 차이점은 값을 저장하는 방식 => 동등 비교 시 차이를 만드는 원인

 

원시 타입은 불변 형태의 값으로 저장하고 그 값은 변수 할당 시점에 메모리 영역 차지

let hello = "hello"
let hi = hello

console.log(hello === hi) // true

 

객체는 변경 가능한 형태로 저장, 값을 복사할 때도 값이 아닌 참조를 전달

객체 간의 비교는 내부값이 같다 하더라도 결과가 true가 아닐 수 있다는 것 인지!

var hello = {
  greet: 'hello',
}

var hi = {
  greet: 'hello',
}

// 동등 비교는 false
console.log(hello === hi) // false
// 원시값 내부 속성값 비교하면 true
console.log(hello.greet === hi.greet) // true

 

Object.is는 두 개의 인수를 받고 이 두 개가 동일한지 확인하는 메서드

'==' 비교는 비교 전 타입이 다르면 강제 형변환 후 비교하지만 Object.is는 X

-0 === +0 // true
Object.is(-0, +0) // false

Number.NaN === NaN // false
Object.is(Number.NaN, NaN) // true

NaN === 0 / 0 // false
Object.is(NaN, 0 / 0) // true

==, ===가 만족하기 못하는 케이스를 추가하기 위해 Object.is가 작동하지만 객체 비교는 ===와 동일하게 동작

Object.is({}, {}) // false

const a = {
  hello: 'hi',
}

const b = a

Object.is(a, b) // true
a === b // true

 

리액트에서는 Object.is로 먼저 비교를 수행하고 객체 간 얕은 비교를 한 번 더 수행

객체 간 얕은 비교는 객체의 첫 번째 깊이에 존재하는 값만 비교한다는 의미

Object.is({hello: 'world'}, {hello: 'world'}) // false

shallowEqual({hello: 'world'}, {hello: 'world'}) // true

 


 

 

함수는 작업 수행, 값 계산 등의 과정으로 표현하고 하나의 블록으로 실행 단위로 만들어 놓은 것

자바스크립트 → Component(props)

리액트 → <Component hello={props.hello}../>

 

함수 선언문

함수 선언문은 어떤 값을 표현하지 않았으므로 표현식이 아니라 일반 문으로 분류

function add(a, b) {
  return a+b
}

하지만 코드 문맥에 따라 선언문으로 표현식을 만들 수도 있다

const sum = function sum(a, b) {
  return a + b
}

sum(10, 20)

 

함수 표현식

일급 객체란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 의미

자바스크립트에서 함수는 일급 객체로 다른 함수의 매개변수, 반환값이 될 수도, 할당도 가능

const sum = function (a, b) {
  return a + b
}

함수 표현식에서는 함수 이름을 생략하는 게 일반적 → 코드 혼란 방지

 

함수 표현식과 선언식의 가장 큰 차이는 호이스팅 여부

함수 호이스팅은 함수에 대한 선언을 실행 전에 미리 메모리에 등록하는 작업

hello() // hello

function hello() {
  console.log('hello')
}

hello() // hello

반면 함수 표현식에서는 호이스팅 되는 시점에서 var의 경우 undefined로 초기화한다

console.log(typeof hello === 'undefined') // true

hello() // Uncaught TypeError: hello is nit a function

var hello = function () {
  console.log('hello')
}

hello()

함수와 다르게 변수는 런타임 이전에 undefined로 초기화되고 할당문이 실행되는 시점에 함수가 할당되어 작동

 

function 생성자

const add = new Funtion('a', 'b', return a + b')

add(10, 20) // 30

생성자 방식으로 함수를 만들면 함수의 클로저 생성 X

 

화살표 함수

const add = (a, b) => {
  return a + b
}

const add = (a, b) => a + b

constructor 사용 X, 즉 생성자 함수로 화살표 함수 사용 불가능

const Car = (name) => {
  this.name = name
}

// Uncaught TypeError: Car is not a constructor
const myCar = new Car('hi')

arguments 존재 X

this는 자신이 속한 객체나 자신이 생성할 인스턴스를 가리키는 값

함수가 일반 함수로서 호출된다면 그 내부의 this는 전역 객체를 가리킨다

화살표 함수는 함수 자체의 바인딩을 갖지 않는다

화살표 함수 내부에서 this를 참조하면 상위 스코프의 this를 그대로 따른다

class Component extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      counter: 1,
    }
  }
  
  functionCountUp() {
    console.log(this) // undefined
    ...
  }
  
  ArrowFuntionCountUp = () => {
    console.log(this) // class Component
    ...
  }
  
  ...
}

화살표 함수의 this는 선언 시점에 결정된다

 

즉시 실행 함수는 함수를 정의하고 그 순간 즉시 실행되는 함수

단 한 번만 호출되고 재호출 불가

(funciton (a, b) {
  return a + b
})(10, 20); // 30

((a, b) => {
    return a + b
  },
)(10, 20) // 30

 

고차 함수

자바스크립트의 함수가 일급 객체라는 특징을 활용하면 함수를 인수로 받거나 결과로 새로운 함수 반환 가능한데 이런 역할을 하는 함수가 고차 함수

const doubleArray = [1, 2, 3].map((item) => item*2)

doubleArray // [2, 4, 6]
// 함수 반환하는 고차함수
const add = function (a) {
  return function (b) {
    return a + b
  }
}

add(1)(2) // 3

고차 함수 컴포넌트를 만들면 컴포넌트 내부에서 공통 관리하는 로직을 분리해 관리 가능해 효율적으로 리팩토링 가능

 

함수의 부수 효과는 함수 내의 작동으로 인해 함수가 아닌 외부에 영향을 끼치는 것 의미

부수 효과가 없는 함수를 순수 함수, 존재하는 함수를 비순수 함수

API 호출과 같이 외부에 영향을 미치는 게 불가피하지만 부수 효과를 최대한 억제하는 방향으로 설계해야 한다

ex) 부수 효과 처리하는 useEffect의 작동을 최소화함으로써 함수의 역할을 좁히고 버그 줄이며 컴포넌트 안정성 높이기

 

하나의 함수에서 너무 많은 일을 하지 않게 하고 재사용성 높이기

함수 이름은 간결하고 이해하기 쉽게 붙이기

ex) useEffect나 useCallback 등의 훅에 넘겨주는 콜백 함수에 네이밍 붙여주면 가독성에 도움

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

 


 

자바스크립트의 클래스란 특정한 형태의 객체를 반복적으로 만들기 위해 사용되는 것

클래스로 하는 모든 것들을 함수로도 동일하게 표현 가능

 

constructor는 생성자로 객체 생성하는 데 사용하는 특수 메서드로 단 하나만 존재 가능

 

프로퍼티는 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값

class Car {
  constructor(name) {
    // 값 받으면 내부 프로퍼티로 할당
    this.name = name
  }
}

const myCar = new Car('자동차') // 프로퍼티 값 넘겨주기

getter는 클래스에서 어떤 값을 가져올 때 사용되는데 get을 앞에 붙여주고 getter 이름 선언

class Car {
  constructor(name) {
    this.name = name
  }
  
  get firstCharactor() {
    return this.name[0]
  }
}

const myCar = new Car('자동차')
myCar.firstCharactor // 자

 

setter은 클래스 필드에 값을 할당할 때 사용하고 set을 먼저 선언하고 이름 붙이기

class Car {
  constructor(name) {
    this.name = name
  }
  
  get firstCharactor() {
    return this.name[0]
  }
  
  set firstCharactor(char) {
    this.name = [char, ...this.name.slice(1)].join('')
  }
}

const myCar = new Car('자동차')
myCar.firstCharactor = '차' // '차' 할당
console.log(myCar.firstCharactor, myCar.name) // 차, 차동차

 

인스턴스 메서드는 클래스 내부에서 선언한 메서드

class Car {
  constructor(name) {
    this.name = name
  }
  
  //인스턴스 메서드
  hello() {
    console.log('안녕 ${this.name}')
  }
}

const myCar = new Car('자동차')
myCar.hello() //안녕 자동차

메서드가 prototype에 선언됐기 때문에 프로토타입 메서드라고도 불린다

Object.getPrototypeOf 사용하면 인수로 넘겨준 변수의 프로토타입 확인 가능

프로토타입 체이닝은 직접 객체에서 선언하지 않아도 프로토타입에 있는 메서드 찾아 실행 도와주는 것

 

정적 메서드는 클래스의 인스턴스가 아닌 이름으로 호출 가능한 메서드

class Car {
  static hello() {
    console.log('hi')
  }
}

const myCar = new Car() // TypeError
Car.hello() // hi

정적 메서드 내부의 this는 클래스 자신을 가리키기 때문에 사용 불가

 

상속

extends는 기존 클래스를 상속받아 자식 클래스에서 상속받은 클래스 기반으로 확장

class Car {
  constructor(name) {
    this.name = name
  }
  honk() {
    // ...
  }
}

class Truck extends Car {
  constructor(name) {
    super(name)
  }
  load() {
    // ...
  }
}

const truck = new Truck('트럭')
truck.honk()
truck.load()

 

자바스크립트 클래스는 프로토타입 기반으로 작동한다

 


 

클로저

함수와 함수가 선언된 어휘적 환경

↑ 선언된 어휘적 환경이라는 것은 변수가 코드 내부에서 어디서 선언되었는지를 말하는 것

작성된 순간에 정적으로 결정

 

스코프 : 변수의 유효 범위

전역 스코프 : 전역 레벨에 선언하는 것으로 변수를 선언하면 어디서든 호출 가능

함수 스코프 : 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다

if(true) {
  var global = 'g'
}

console.log(global) // 'g'
console.log(global === window.global) // true

↑ var global이 { } 안에 선언되었는데 밖에서도 접근 가능한 건 함수 레벨 스코프를 가지기 때문

function hello() {
  var local = 'l'
  console.log(local) // 'l'
}

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

 

함수 레벨 스코프를 활용해 어떤 작업을 할 수 있다는 개념이 바로 클로저

리액트가 관리하는 내부 상태 값은 리액트가 별도로 관리하는 클로저 내부에서만 접근 가능

클로저를 활용하면 전역 스코프의 사용을 막고 개발자가 원하는 정보만 원하는 방향으로 노출 가능

 

function Component() {
  const [state, setState] = useState()
  
  function handleClick() {
    setState((prev) => prev + 1)
  }
}

useState가 반환한 setState는 useState의 호출이 끝났음에도 state가 저장돼 있는 곳을 기억하기 때문에 계속 state값 사용 가능

 

for(let i=0;i < 5;i++) {
  setTimeout(function() {
    console.log(i)
  }, i*1000)
}

let을 써야 블록 레벨 스코프를 가지게 돼서 let i가 for문을 돌면서 각각의 스코프를 갖게 된다(var X)

 

외부 함수를 기억하고 이를 내부 함수에서 가져다 쓰는 메커니즘은 성능에 영향을 미치기 때문에 주의 필요

 


 

싱글 스레드 자바스크립트

→ 한 번에 하나의 작업만 동기 방식으로 처리

 

동기는 직렬 방식으로 작업 처리

비동기는 병렬 방식으로 작업을 처리해 한 번에 여러 작업 실행 가능

 

프로세스란 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업 단위

하나의 프로그램 실행은 하나의 프로세스를 가지고 그 프로세스 내부에서 모든 작업 처리

프로세스보다 더 작은 실행 단위가 스레드

프로세스 내부에서 여러 개의 스레드를 활용하면서 동시 다발적인 작업 처리 가능!

 

멀티 스레드는 내부적으로 처리가 복잡하다는 단점, 동시성 문제

자바스크립트가 싱글 스레드라는 것은 자바스크립트 코드의 실행이 하나의 스레드에서 순차적으로 이루어진다는 뜻

하나의 작업이 끝나기 전까지 뒤이은 작업이 실행되지 않는다는 것을 의미

자바스크립트의 모든 코드는 동기식으로 한 번에 하나씩 순차적 처리

 

비동기는 요청한 즉시 결과가 주어지지 않을 수도 있고 응답이 언제 올지 알 수 없다

하지만 여러 작업을 동시에 수행 가능

 

이벤트 루프는 비동기 실행을 돕기 위해 만들어진 장치로 호출 스택이 비어 있는지 여부 확인

호출 스택은 수행해야 할 코드나 함수를 순차적으로 담아두는 스택

이벤트 루프는 호출 스택 내부에 수행해야 할 작업을 확인하고 수행해야 할 코드가 있으면 엔진으로 실행

코드 실행과 호출 스택이 비어있는지 확인하는 것 모두 단일 스레드에서 순차적으로 발생

 

태스크 큐란 실행해야 할 태스크의 집합 → 실행해야 할 태스크는 비동기 함수의 콜백 함수나 이벤트 핸들러 등

이벤트 루프는 호출 스택에 실행 중인 코드 확인, 태스크 큐에 대기 중인 함수 반복 확인

호출 스택이 비었다면 태스크 큐에 대기 중인 작업 확인하고 오래된 것부터 실행

 

비동기 함수는 태스크 큐가 할당되는 별도의 스레드에서 수행

이 별도 스레드에서 태스크 큐에 작업을 할당해 처리하는 것은 브라우저나 Node.js의 역할

 

자바스크립트 코드 실행은 싱글 스레드에서 이루어지지만 외부 웹 API 등은 모드 코드 외부에서 실행되고 콜백이 태스크 큐로 들어가는 것

이벤트 루프는 호출 스택이 비고 콜백이 실행 가능한 때가 오면 이걸 꺼내서 수행하는 역할을 하는 것

 

이벤트 루프는 하나의 마이크로 태스크 큐를 가지는데 기존 태스크 큐와는 다른 태스크 처리, 기존 태스크 큐보다 우선

대표적인 마이크로 태스크로 Promise가 있다

setTimeout,  setInterval은 Promise보다 늦게 실행

→ 마이크로 태스크 큐가 빌 때까지 기존 태스크 큐의 실행은 뒤로 미뤄진다

 

function foo() {
  console.log('foo')
}
function bar() {
  console.log('bar')
}
function baz() {
  console.log('baz')
}

setTimeout(foo, 0)
Promise.resolve().then(bar).then(baz)

↑ 실행하면 bar, baz, foo 순으로 실행

 

태스크 큐 : setTimeout, setInterval, setImmediate

마이크로 태스크 큐 : process.nextTick, Promises, queueMicroTask, MutationObserver

 

각 마이크로 태스크 큐 작업이 끝날 때마다 한 번씩 렌더링 할 기회를 얻는다

 

동기 코드는 물론 마이크로 태스크 또한 렌더링에 영향을 미칠 수 있다

특정 렌더링이 무거운 작업과 연관이 있다면 어떤 식으로 분리하면 좋을지 고민!!

 

바벨은 자바스크립트의 최신 문법을 다양한 브라우저에서도 일관적으로 지원할 수 있도록 코드를 트랜스파일한다

 


 

구조 분해 할당이란 배열 또는 객체의 값을 분해해 개별 변수에 즉시 할당하는 것

const array = [1, 2, 3, 4, 5]

const [first, second, third, ...arrayRest] = array
// first 1
// second 2
// third 3
// arrayRest [4, 5]

 

배열의 구조 분해 할당은 ,의 위치에 따라 값이 결정

const array = [1, 2, 3, 4, 5]

const [first, , , , fifth] = array
// 2, 3, 4는 아무 표현식이 없으므로 변수 할당 생략

first // 1
fifth // 5

↑ 실수 유발 가능성이 크기 때문에 배열 길이가 작을 때 주로 사용

 

배열 구조 분해 할당에 기본값 선언도 가능

const array = [1, 2]
const [a=10, b=10, c=10] = array
// a 1
// b 2
// c 10

 

※ 반드시 undefined일 때만 기본값 사용

const [a = 1, b = 1, c = 1, d = 1, e = 1] = [undefined, null, 0, '']
a // 1
b // null
c // 0
d // ''
e // 1

↑ 위에서 기본값을 사용하는 것은 a와 e뿐

 

특정값 이후의 값을 다시 배열로 선언하고 싶다면 전개 연산자 ... 사용

어디서부터 어디까지 할당할지 예측 가능한 뒤쪽에서만 가능

const array = [1, 2, 3, 4, 5]
const [first, ...rest] = array

// first 1
// rest [2, 3, 4, 5]

 

객체 구조 분해 할당은 객체에서 값을 꺼내온 뒤 할당하는 것 의미

객체는 배열과 달리 객체 내부 이름으로 꺼내온다는 차이가 있다

const object = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  e: 5,
}

const {a, b, c, ...objectRest} = object
// a 1
// b 2
// c 3
// objectRest = {d: 4, e: 5}

 

새로운 이름으로 다시 할당도 가능

const object = {
  a: 1,
  b: 2,
}
const { a: first, b: second } = object

// first 1
// second 2

 

기본값도 주는 것도 가능

const array = {
  a: 1,
  b: 1,
}

const { a = 10, b = 10, c = 10 } = object

// a 1
// b 1
// c 10

↑ props에서 값을 바로 꺼내올 때 자주 쓰는 방식

function SampleComponent({ a, b }) {
  return a + b
}

SampleComponent({ a: 3, b: 5 }) // 8

 

변수에 있는 값으로 꺼내오는 계산된 속성 이름 방식도 가능

const key = 'a'
const object = {
  a: 1,
  b: 1,
}

const { [key]: a } = object

// a = 1

계산된 속성 이름을 사용하려면 반드시 이름을 선언하는 :a와 같은 변수 네이밍이 필요

 

 

전개 구문은 배열, 객체, 문자열과 같은 순회할 수 있는 값에 대해 전개해 간결하게 사용할 수 있는 구문

 

배열의 전개 구문

const arr1 = ['a', 'b']
const arr2 = [...arr1, 'c', 'd', 'e'] // ['a', 'b', 'c', 'd' , 'e']

배열 내부에서 ...배열을 사용하면 배열을 마치 전개하는 것처럼 선언하고 내부 배열에서 활용 가능

이를 활용해 기존 배열에 영향을 미치지 않고 배열 복사도 가능

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

arr1 === arr2 // true 내용이 아닌 참조를 복사하기 때문

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

arr1 === arr2 // 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 }

객체 합성하는 데 편리하다

객체 전개 구문에 있어 순서가 중요하다

전개 구문 이후에 값 할당이 있다면 할당한 값이 이전에 전개했던 구문 값을 덮어쓴다

 

객체 초기자는 객체를 선언할 때 객체에 넣고자 하는 키와 값을 가지고 있는 변수가 이미 존재한다면 해당 값을 간결하게 넣어줄 수 있는 방식

const a = 1
const b = 2

const obj = {
  a,
  b,
}

// {a: 1, b: 2}

원래대로라면 a: a 같은 형식으로 작성해야 하는데 위와 같은 방식으로 축약해 선언도 가능

 

Array.prototype.map

인수로 전달받은 배열과 똑같은 길이의 새로운 배열 반환하는 메서드

배열의 각 아이템을 순회하면서 각 아이템을 콜백으로 연산한 결과로 구성된 새로운 배열 생성

리액트에서는 주로 특정 배열을 기반으로 어떤 리액트 요소를 반환하고자 할 때 많이 사용

const arr = [1, 2, 3, 4, 5]
const doubledArr = arr.map((item) => item * 2)
// [2, 4, 6, 8, 10]
const arr = [1, 2, 3, 4, 5]
const Elements = arr.map((item) => {
  return <Fragment key={item}>{item}</Fragment>
}

 

Array.prototype.filter

콜백 함수를 인수로 받는데 이 함수에서 truthy 조건을 만족하는 경우에만 해당 원소 반환하는 메서드

filter의 결과에 따라 원본 배열 길이 이하의 새로운 배열이 반환

주로 기존 배열에 대해 어떤 조건을 만족하는 새로운 배열을 반환할 때 사용

const arr = [1, 2, 3, 4, 5]
const evenArr = arr.filter((item) => item % 2 === 0)
// [2, 4]

 

Array.prototype.filter

콜백 함수와 함께 초깃값을 추가로 받는데 이 초깃값에 따라 배열이나 객체, 또는 그 외의 다른 무언가를 반환할 수 있는 메서드

reducer 콜백 함수를 실행하고 이를 초깃값에 누적해 결과 반환

const arr = [1, 2, 3, 4, 5]
const sum = arr.reduce((result, item) => {
  return result + item
}, 0)
// 15

0은 reduce의 결과를 누적할 초깃값

reducer 콜백 함수의 첫 번째 인수는 초깃값의 현재값을 의미하고 두 번째 인수는 현재 배열의 아이템을 의미

콜백의 반환값을 계속해서 초깃값에 누적하면서 새로운 값을 만든다

 

Array.prototype.forEach

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

const arr = [1, 2, 3]

arr.forEach((item) => console.log(item))
// 1, 2, 3

forEach는 반환값이 없다

콜백 함수 내부에서 아무리 반환해도 forEach의 반환값은 undefined로 의미 없다는 뜻

실행되는 순간 에러를 던지거나 프로세스를 종료하지 않는 이상 이를 멈출 수 없다

 

삼항 조건 연산자

const value = 10
const result = value % 2 === 0 ? '짝수' : '홀수'
// 짝수

조건문 ? 참일 때 값 : 거짓일 때 값

JSX 내부에서 조건부로 렌더링 하기 위해 가장 널리 쓰인다

function Component({ condition }) {
  return <>{condition ? '참' : '거짓'}</>
}

 


 

타입스크립트는 기존 자바스크립트 문법에 타입을 가미한 것

자바스크립트는 동적 타입의 언어라서 대부분의 에러를 코드를 실행했을 때만 확인 가능

타입 체크를 정적으로 런타임이 아닌 빌드 타임에 수행할 수 있게 해준다

타입스크립트는 변수에 타입 설정을 할 수 있어 굳이 런타임까지 가지 않더라도 코드를 빌드하는 시점에 이미 에러 발생 가능성이 있는 코드를 확인 가능하다

 

 

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

 

any 대신 unknown 사용

불가피하게 아직 타입을 단정할 수 없는 경우 top type인 unknown 사용하는 게 좋다

any는 정말 예외적인 경우에만 사용하는 것이 좋다

하지만 any와는 다르게 이 값을 바로 사용하는 것은 불가능하기 때문에 unknown으로 선언된 변수를 사용하려면 타입을 원래 의도했던 대로 적절히 좁혀야 한다

function doSomething(callback: unknown) {
  if(typeof callback == 'function') {
    callback()
    return
  }
  throw new Error('callback은 함수여야 합니다')
}

이렇게 unknown에 직접 접근이 아닌 해당 값이 원하는 타입일 때만 의도대로 작동하도록 하면 예상치 못한 타입을 받아들일 수 있음은 물론 사용하는 쪽에서도 더욱 안전하게 사용 가능

 

bottom type으로는 never이 있는데 이건 어떠한 타입도 들어올 수 없음을 의미

type what1 = string & number
type what2 = ('hello' | 'hi') & 'react'

위 두 경우 모두 둘 다 만족시키는 타입은 존재하지 않기 때문에 never가 선언된다

 

타입 가드 적극 활용하기

타입을 사용하는 쪽에서 최대한 타입을 좁히는 것이 좋은데 이걸 도와주는 게 바로 타입 가드

조건문과 함께 타입 가드를 사용하면 좀 더 명확하게 변수나 함수 사용 가능

 

instanceof는 지정한 인스턴스가 특정 클래스의 인스턴스인지 확인할 수 있는 연산자

unknown으로 내려오는 에러에 대해 타입 가드를 통해 타입을 좁힘으로써 각 에러에 따라 원하는 처리 가능

async function fetchSomething() {
  try {
    const response = await fetch('/api/something')
    return await response.json()
  } catch(e) {
    // e는 unknown
    
    //UnAuthorizedError를 위한 타입 가드 조건문
    if (e instanceof UnAuthorizedError) {
      // do something...
    }
    
    throw e
  }

 

typeof 연산자는 특정 요소에 대해 자료형 확인하는 데 사용 가능

if(tyepof value === 'string') {
  console.log(value)
}
if(typeof vlaue === 'undefined') {
  return
}

 

in은 property in object로 사용되는데 주로 어떤 객체에 키가 존재하는지 확인하는 용도로 사용

interface Student {
  age: number
  score: number
}

function doSchool(person:Student) {
  if('age' in person) {
    person.age
    person.score
  }
}

 

 

제네릭은 함수나 클래스 내부에서 단일 타입이 아닌 다양한 타입에 대응할 수 있도록 도와주는 도구

타입만 다른 비슷한 작업을 하는 컴포넌트를 단일 제네릭 컴포넌트로 선언해 간결하게 작성 가능

function Component() {
  // state: string
  const [state, setState] = useState<string>('')
  // ...
}

useState에 제네릭으로 타입을 선언하면 state 사용과 기본값 선언을 좀 더 명확하게 할 수 있다

제네릭을 하나 이상 사용도 가능

 

인덱스 시그니처는 객체의 키를 정의하는 방식

type Hello = {
  [key: string]: string
}

const hello: Hello = {
  hello: 'hello',
  hi: 'hi',
}

hello['hi'] // hi
hello['안녕'] // undefined

객체의 키는 동적으로 선언되는 경우를 최대한 지양해야 하고 객체의 타입도 필요에 따라 좁혀야 한다

 

 

※ 타입스크립트 전환 가이드

 

tsconfig.json 먼저 작성하기

최상위 디렉터리에 tsconfig.json 생성

{
  "compilerOptions": {
    "outDir": "./dist",
    "allowJs": true,
    "target": "es5"
  },
  "includes": ["./src/**/*"]
}

- outDir은 .ts나 .js가 만들어진 결과를 넣어두는 폴더, tsc는 타입스크립트를 자바스크립트로 변환하는 명령어인데 이 tsc를 사용하면 결과물이 outDir로 넘어간다

- allowJs는 .js 파일을 허용할지 여부

- target에는 결과물이 될 자바스크립트 버전 지정

- include에는 트랜스파일할 자바스크립트와 타입스크립트 파일 지정

 

JSDoc과 @ts-check를 활용해 점진적으로 전환하기

자바스크립트 파일을 타입스크립트로 전환하지 않고 타입 체크하려면

파일 최상단에 //@ts-check 선언하고 JSDoc를 활용해 변수나 함수에 타입 제공하면 컴파일러가 타입 확인

// @ts-check

/**
 * @type {string}
 */
const str = true

/**
 * @param {number} a
 * @param {number} b
 * @return {number}
 */
 function sum(a, b) {
   return a + b
 }

 

타입 기반 라이브러리 사용을 위해 @types 모듈 설치하기

타입스크립트로 작성되지 않은 코드에 대한 타입을 제공하는 라이브러리

 

파일 단위로 조금씩 전환하기

파일을 .ts로 고칠 때 가장 먼저 전환해 볼 파일은 상수나 유틸과 같이 별도의 의존성을 가지고 있지 않은 파일이다

 

728x90