본문으로 건너뛰기
yceffort
PostsSeriesTagsAbout🧪 Research
EN

Tweaks

theme
accent palette
film grain
minimal mode
BACK TO INDEX
◆ ESSAY
--min
--year
KOoriginal

mailMail icongithubtwitter
yceffort
•
© 2026
•
https://yceffort.kr
BACK TO INDEX
◆ ESSAY

알아두면 유용한 타입스크립트 팁

avatar
yceffort
2022-05-08 · 6분
6min
2022year
KOoriginal
typescript

제네릭 활용하기

테이블 컴포넌트가 있고, 여기에 props를 할당해서 그린다고 생각해보면, 보통은 이런 코드가 나올 po`것이다.

import React from 'react'

interface Props {
  items: Array<{id: string}>
  renderItem: (item: {id: string}) => React.ReactNode
}

export const Table = (props: Props) => {
  return null
}

export const Component = () => {
  return (
    <Table
      items={[{id: '1'}]}
      renderItem={(item) => {
        return null
      }}
    />
  )
}

하지만 이런 구조는 {id: string} 으로 고정되어 있어,여러가지 종류의 props를 그리기에는 무리가 있다. 이럴 때 사용하면 좋은 것이 Generic이다. Generic은 타입, 인터페이스 등에서 외부에서 정의된, 공통의 속성을 사용하고 싶을 때 유용하다.

import React from 'react'

interface Props<TItem> {
  items: Array<TItem>
  renderItem: (item: TItem) => React.ReactNode
}

export const Table = <TItem,>(props: Props<TItem>) => {
  return null
}

export const Component = () => {
  return (
    <>
      <Table
        items={[{id: '1'}]}
        // item이 {id: "1"}로 추론되는 것을 볼 수 있다.
        renderItem={(item) => {
          return null
        }}
      />
      <Table
        items={[{id: '1', name: 'yceffort'}]}
        // 서로 다른 props가 와도 문제 없다.
        renderItem={(item) => {
          return null
        }}
      />
    </>
  )
}

이렇게 props와 interface내에서 사용하는 것 뿐 만 아니라, 타입을 아직 알 수 없는 객체 등을 다룰 때도 유용하다.

export const getDeepValue = (obj: any, firstKey: string, secondKey: string) => {
  return obj[firstKey][secondKey]
}

const obj = {
  foo: {
    a: true,
    b: 2,
  },
  bar: {
    c: '12',
    d: 18,
  },
}

const value = getDeepValue(obj, 'foo', 'a')

// value any

어떠한 객체의 키로 특정한 값을 가져온다고 생각해보자. 객체의 형태를 당장 알 수 없으므로 any를 두고, 키는 string으로 두었다. any를 쓰는 것은 타입스크립트에서 최대한 자제해야하는 행위다.

이것을 해결하기 위해, 마찬가지로 Generic을 사용할 수 있다.

export const getDeepValue = <
  TObj,
  TFirstKeyOfObj extends keyof TObj,
  TSecondKeyOfObj extends keyof TObj[TFirstKeyOfObj],
>(
  obj: TObj,
  firstKey: TFirstKeyOfObj,
  secondKey: TSecondKeyOfObj,
) => {
  return obj[firstKey][secondKey]
}

const obj = {
  foo: {
    a: true,
    b: 2,
  },
  bar: {
    c: '12',
    d: 18,
  },
}

const value = getDeepValue(obj, 'foo', 'a')
// value number

extends을 활용하면 기존의 제네릭을 상속 받아 또 다른 제네릭을 만들 수 있다. TFirstKeyOfObj는 keyof TObj, 즉 TObj의 키를 상속받아 만들었고, TSecondKeyOfObj는 TObj[TFirstKeyOfObj]의 키를 상속받아 만든 제네릭이다. 처음보기엔 무언가 복잡해보이지만, 차분하게 읽어보면 별거 없다는 것을 알 수 있다.

조건부 타입

타입도 다른 변수들이나 표현과 마찬가지로 조건부로 만들 수 있다.

type Animal = {
  name: string
}

type Human = {
  firstName: string
  lastName: string
}

type GetRequiredInformation<TType> = any

export type RequiredInformationForAnimal = GetRequiredInformation<Animal>

export type RequiredInformationForHuman = GetRequiredInformation<Human>

GetRequiredInformation에서 받은 제네릭 TTYpe이 Animal인지, Human인지 확인하여 새로운 타입을 extends할 수 있는 타입을 만들어보자.

type GetRequiredInformation<TType> = TType extends Animal
  ? {age: number}
  : {salary: number}

extends를 사용하면 단순히 상속하는 것 뿐만 아니라, 마치 조건문으로 사용해서 상속할 수 있는지 여부도 확인할 수 있다. 이에 따라 타입별로 원하는 추가 타입을 선언해 줄 수 있다.

추가로, GetRequiredInformation에 Animal Human외에 다른 것이 오는 것을 막고 싶다면, 아래와 같이 never를 사용하면 된다.

type GetRequiredInformation<TType> = TType extends Animal
  ? {age: number}
  : TType extends Human
    ? {salary: number}
    : never

과거 글에서 이야기 했던 것처럼, 그 어떤 것도 사용할 수 없는 불가능한 타입, bottom type을 만들고 싶을 때 never를 사용한다.

이러한 방식을 조금더 응용하면, 내가 타입스크립트 컴파일러에 사용할 수 있는 에러도 만들 수 있다.

export function deepEqualCompare(a: any, b: any) {
  if (Array.isArray(a) || Array.isArray(b)) {
    throw new Error('배열은 비교할 수 없습니다.')
  }
  return a === b
}
export function deepEqualCompare<Arg>(a: Arg extends any ? "배열은 비교할 수 없습니다", b: Arg) {
  if (Array.isArray(a) || Array.isArray(b)) {
    throw new Error("배열은 비교할 수 없습니다.")
  }
  return a === b
}

deepEqualCompare([1, 2, 3], [1]) // Argument of type 'number[]' is not assignable to parameter of type '"배열은 비교할 수 없습니다."'.(2345)

그러나, 이러한 코드가 동작해버리는 참사가 발생버리기 때문에, never를 쓰는 것이 좋다.

deepEqualCompare('배열은 비교할 수 없습니다.', '배열은 비교할 수 없습니다.') // ????
// 물론 코드가 잘못된 것은 아니지만, 우리가 원하는 바는 이게 아닐 것이다.

타입스크립트의 타입을 공부할 때 도움이 되는 것들

  • ts-belt: 타입스크립트에서 유용하게 사용할 수 있는 다양탄 유틸리티 라이브러리를 제공한다. 찾아보면 별에 별 유틸리티 타입들을 다 제공하는데, 이를 어떻게 만들었을지 상상해 보는 재미가 있다.
  • zod: joi의 타입스크립트 버전이라고 보면된다. 타입스크립트의 스키마를 체크하는데 도와주는 라이브러리다.
  • type-challenges: 알고리즘에 백준이 있다면, 타입스크립트에는 type-challenge가 있다. 문제를 하나씩 풀어나가는 재미가 있다. hard까지는 그럭저럭 꾸역꾸역할 수 있었는데, extreme부터는 약간 그냥 테스트를 위한 테스트 같은 느낌이다. (내가 못풀어서 그런 걸수도 있다.) 실무에서 개발하는 타입스크립트 개발자라면, medium까지만 풀어도 충분할 것 같다.
  • TypeScript Error Translator: 타입스크립트를 처음 접했을 때 많이 헤매는 것이 잘못된 타입으로 인한 에러인데, 이 에러를 읽기가 처음에는 약간 버거운 경우도 있다. 이러한 불친절한 에러를 사람이 읽기 쉽게 번역해주는 extension이다.

관련 글

  • #typescript#oxc#eslint

    typescript@7을 설치하면 벌어지는 일들: 블로그 모노레포 마이그레이션 기록

    pnpm lint가 12분 32초 걸리던 모노레포에 typescript 7.0.2를 넣어봤다. 타입체크는 조용히 지나갔는데 next build가 깨졌고, lint는 크래시했다. eslint와 prettier를 oxlint와 oxfmt로 갈아탄 하루의 연쇄 반응과 전후 실측 기록. 미리 말해두면, 빌드는 빨라지지 않았다.

    2026-08-10·17분
  • #typescript#backend

    Effect 시스템 심층 분석: 모나드에서 Algebraic Effects까지, 그리고 Effect-TS의 선택

    Effect-TS가 대체 뭔데 다들 난리인지 직접 파헤쳐봤다.

    2026-02-20·41분
  • #typescript

    TypeScript에서 switch문의 모든 케이스를 빠짐없이 처리했는지 검사하는 방법

    never 타입을 활용한 exhaustive check 패턴

    2026-01-17·13분
  • #react#typescript

    useEvent에서 useEffectEvent까지: React의 이벤트 핸들러 안정화 여정

    3년 전 RFC가 드디어 빛을 보다

    2025-12-15·24분

새 글을 놓치고 싶지 않으시다면 RSS로 구독해 주세요.

RSS 구독 →

yceffort — 프론트엔드 엔지니어입니다. 발표·기술 자문·기고 문의는 이곳에서 받고 있습니다.

← Back to the blogIssue on GitHub →