모던 리액트 Deep Dive - 타입 스크립트

2025. 9. 23. 21:16·Study/모던 리액트 Deep Dive

⚡리액트 개발을 위해 꼭 알아야할 자바스크립트 - 1.7 타입스크립트

1.7.1 타입스크립트란

TypeScript is JavaScript with syntax for types
  • 기존 자바스크립트 문법에 타입을 가미한 것
  • 자바스크립트는 동적 타입의 언어이기 때문에 대부분의 에러를 코드를 실행했을 때만 확인할 수 있다는 문제점
    • 동적 타입 언어라는 점은 개발자에게 자유를 주기도 하지만 코드의 규모가 커질수록 오히려 발목을 잡는 경우도 많음
  • 타입스크립트는 타입 체크를 정적으로 런타임이 아닌 빌드 타입에 수행할 수 있게 해줌
  • 모든 함수와 변수에 타입 확인 연산자인 typeof를 적용해서 체크하는 것은 너무 번거롭고 코드의 크기를 과도하게 키움
function test(a,b) {
	return a/b
}

test(5,2) // 2.5
test('안녕하세요','하이')// NaN


function test(a,b) {
	if (typeof a !== 'number' || typeof b !== 'number'){
    	throw new Error('a,b 모두 숫자여야함!')
    }
    return a/b
}

🌓인수가 객체라면 내부 프로퍼티에 대해 모두 체크할 것인가?

function test(a: number, b: number) {
    return a / b
}

test('안녕하세요', '하이')
  • 위의 코드에서 a와 b 변수에 number 타입을 지정하면 런타임까지 가지 않더라도 코드를 빌드하는 시점에 에러가 발생할 수 있는 코드를 확인할 수 있음
  • 타입스크립트는 자바스크립트의 슈퍼셋으로서 함수의 반환 타입, 배열, enum 등 기존에는 사용하기 어려웠던 타입 관련 작업들을 손쉽게 처리할 수 있음
  • 자바스크립트에서 불가능한 일은 타입스크립트에서도 마찬가지로 불가능

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

🌚 any 대신 unknown를 사용하자

  • 처음 작성할 때 저지르는 실수  : any를 자주 사용
  • any는 정말 불가피할 때만 사용해야 하는 타입
  • any를 사용한다는 것은 타입스크립트가 제공하는 정적 타이핑의 이점을 모두 버리는 것
function doSomething(callback: any) {
    callback()
}

// 타입스크립트에서 에러가 발생하지 않는다. 그러나 이 코드는 실행 시 에러가 발생한다.
doSomething(1)
doSomething은 callback을 인수로 받는데 해당 타입이 any로 되어 있어서 실제 함수가 아닌 값이 들어가도 타입스크립트가 에러를 발생시키지 않음
  • any는 자바스크립트에서 타입스크립트로 넘어가는 과도기와 같은 정말로 예외적인 경우에만 사용하는 것이 좋음
  • 타입을 단정할 수 없는 경우에는 unknown 사용하는 것이 좋음
  • unknown은 모든 값을 할당할 수 있는 top type으로, 어떠한 값을 할당할 수 있지만 any와 다르게 바로 사용하는 것은 불가능함
function doSomething(callback: unknown) {
    callback() // 'callback' is of type 'unknown'
}
callback은 unknown, 즉 아직 알 수 없는 값이기 때문에 사용할 수 없다는 내용
unknown으로 선언된 변수를 사용하기 위해서는 type narrowing, 즉 타입이 원래 의도했던 대로 적절히 좁혀야 함
function doSomething(callback: unknown) {
    if (typeof callback === 'function') {
        callback()
        return
    }
    
    throw new Error('callback은 함수여야 합니다.')
}
  • unknown을 사용하는 것은 예상치 못한 타입을 받아들일 수 있어 사용하는 쪽에서도 더욱 안전하게 사용할 수 있음
  • any보다는 unknown을 사용하는 습관을 들이는 것이 좋음
  • unknown과는 반대로 never타입이 있는데, never는 어떠한 타입도 들어올 수 없음을 의미

실제 리액트 코드에서는 props는 없지만 state가 존재하는 상황에서 빈 props를 통해 어떤 props도 받지 않는다는 의미로 사용

import React from "react";
import logo from "./logo.svg";
import "./App.css";
type Props = Record<string, never>;
type State = {
  counter: 0;
};

class SampleComponent extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = {
      counter: 0,
    };
  }

  render() {
    return <>...</>;
  }
}

function App() {
  return (
    <>
      <SampleComponent />
      <SampleComponent hello="world" /> //에러를 발생시킴
    </>
  );
}

export default App;
위 SampleComponent는 어떠한 props도 받을 수 없는 대신 state가 존재함
React.Component의 제네릭은 Props와 State를 순서대로 작성해야 하는데, Props의 경우 Record<string,never>로 작성해 어떠한 props도 받을 수 없도록 타입 스크립트로 처리할 수 있음

 

🌚 타입 가드를 적극 활용하자

  • 타입을 사용하는 쪽에서는 최대한 타입을 좁히는 것이 좋음
  • 타입 가드 : 타입을 좁히는데 도움을 주는것
  • 조건문과 함께 타입가드를 사용하면 타입을 효과적으로 좁힐 수 있어 조금 더 변수나 함수를 사용할 수 있음

🌓 instanceof와 typeof

  • instanceof : 지정한 인스턴스가 특정 클래스의 인스턴스인지 확인할 수 있는 연산자
class UnAuthorizedError extends Error {
  constructor() {
    super()
  }
    
  get message() {
    return '인증에 실패했습니다.'
  }
}

class UnExpectedError extends Error {
  constructor() {
    super()
  }
    
  get message() {
    return '예상치 못한 에러가 발생했습니다.'
  }
}

async function fetchSomething() {
  try {
    const response = await fetch('/api/something');
    return await response.json();
  } catch (e) {
    // e는 unknown이다.
        
    // UnAuthorizedError를 위한 타입 가드 조건문
    if (e instanceof UnAuthorizedError) {
      ...
    }
        
    // UnExpectedError를 위한 타입 가드 조건문
    if (e instanceof UnExpectedError) {
      ...
    }
        
    throw e;
    }
}
  • unknown으로 내려오는 에러에 대해 타입 가드를 통해 타입을 좁힘으로써 각 에러에 따라 원하는 처리 내용을 추가 할 수 있음
  • typeof 연산자는 특정 요소에 대해 자료형을 확인하는데 사용함
function logging(value: string | undefined) {
  if (typeof value === 'string') {
    console.log(value);
  }
    
  if (typeof value === 'undefined') {
    return;
  }
}

🌓 in

  • property in object로 사용 
  • 어떤 객체에 키가 존재하는지 확인하는 용도
  • 타입에 여러가지 객체가 존재할 수 있는 경우가 유용함
interface Student {
  age: number;
  score: number;
}

interface Teacher {
  name: string;
}

function doSchool(person: Student | Teacher) {
  if ('age' in person) {
    person.age // person은 Student
    person.score
  }
  
  if ('name' in person) {
    person.name // person은 Teacher
  }
}

🌚제네릭

  • 함수나 클래스내부에서 단일 타입이 아닌 다양한 타입에 대응할 수 있도록 도와주는 도구
  • 제네릭을 사용하면 타입만 다른 비슷한 작업을 하는 컴포넌트를 단일 제네릭 컴포넌트로 선언해 작성할 수 있음
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

💡 리액트에서 제네릭을 사용할 수 있는 코드 > useState

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

제네릭을 하나이상 사용할 수 있음 단, 제네릭을 사용할 땐 적절한 네이밍하는 것이 좋음

🌚 인덱스 시그니처 

  • 객체의 키를 정의하는 방식
  • 키에 원하는 타입을 부여할 수 있음
  • 동적인 객체를 정의할 때 유용하나, 존재하지 않는 키로 접근하면 undefined를 반환 할 수 있음
type Hello = {
  [key: string] : string //인덱스 시그니처
}

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

hello['hi'] // hi
hello['안녕하세요'] // undefined

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

💡 객체의 키를 좁히는 두가지 방법

//  record를 사용
type Hello = Record<'hello'|'hi', string>

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


// 타입을 사용한 인덱스 시그니처
type Hello = { [key in 'hello'|'hi']: string }

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

💡인덱스 시그니처를 사용할 때 마주하는 이슈

Object.keys(hello).map((key) => {
  // Element implicitly has an 'any' type because expression of type 'string'
  // can't be used to index type 'hello'
  // No index signature with a parameter of type 'string' was found on type 'Hello'
  const value = hello[key];
  return value;
});

🌓해결방법

//1. bejct.keys(hello)를 as로 타입 단언
(Object.keys(hello) as Array<keyof Hello>).map((key) => {
  const value = hello[key];
  return value;
});

//2. 타입스크립트의 Object.keys에 대한 반환 타입을 string[] 대신 개발자가 단언한 타입으로 강제하는 방법
// 타입가드 함수를 만드는 방법
function keysOf<T extends Object>(obj: T): Array<keyof T> {
  return Array.from(Object.keys(obj)) as Array<keyof T>;
}

keysOf(hello).map((key) => {
  const value = hello[key];
  return value;
})

//3. 가져온 key를 단언 하는 방법
Object.keys(hello).map((key) => {
  const value = hello[key as keyof Hello];
  return value;
})

 

 

'Study > 모던 리액트 Deep Dive' 카테고리의 다른 글

모던 리액트 Deep Dive - 가상 DOM과 리액트 파이버  (0) 2025.09.30
모던 리액트 Deep Dive - JSX  (0) 2025.09.30
모던 리액트 Deep Dive - 자주 사용되는 자바스크립트 문법  (0) 2025.09.23
모던 리액트 Deep Dive - 이벤트 루프와 비동기 통신의 이해  (0) 2025.09.23
모던 리액트 Deep Dive - 클로저  (1) 2025.09.15
'Study/모던 리액트 Deep Dive' 카테고리의 다른 글
  • 모던 리액트 Deep Dive - 가상 DOM과 리액트 파이버
  • 모던 리액트 Deep Dive - JSX
  • 모던 리액트 Deep Dive - 자주 사용되는 자바스크립트 문법
  • 모던 리액트 Deep Dive - 이벤트 루프와 비동기 통신의 이해
happy_dev
happy_dev
복사하고 붙여넣기 잘하고 싶어요
  • happy_dev
    happy의 개발일지
    happy_dev
  • 전체
    오늘
    어제
    • 전체 (43)
      • Java (0)
      • React (1)
      • DB (1)
      • Study (41)
        • 친절한 SQL 튜닝 (9)
        • 모던 리액트 Deep Dive (18)
        • 젠킨스로 배우는 CI,CD 파이프라인 구축 (14)
        • Studyd (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    DB
    인덱스의기본
    Jenkins
    리액트
    SQL
    oracle
    toad
    젠킨스
    조인
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
happy_dev
모던 리액트 Deep Dive - 타입 스크립트
상단으로

티스토리툴바