오늘은 프론트엔드 개발에서 절대 빠질 수 없는 **타입스크립트(TypeScript)**를 단순히 '에러 안 나는 수준'을 넘어, 실무에서 진짜 강력한 무기로 사용하는 방법에 대해 이야기해볼게. 내가 9년 동안 프론트엔드 개발을 하면서 수많은 주니어들의 코드를 봐왔는데, 단순히 타입을 선언하는 것과 타입 시스템을 제대로 활용하는 것은 생산성에서 엄청난 차이를 만들더라고.

software development workspace

1. API 응답에는 '판별 가능한 유니온(Discriminated Unions)'을 써봐

실무에서 가장 에러가 많이 나는 곳이 어디일까? 바로 백엔드와의 API 통신 경계선이야. 네트워크 요청은 성공할 수도 있고, 실패할 수도 있잖아. 이걸 단순히 옵셔널 파라미터(?)로 처리하면, 성공했을 때도 에러 데이터를 체크해야 하는 번거로움이 생겨. 이럴 때 판별 가능한 유니온을 사용하면 아주 깔끔해져. 공통된 리터럴 타입 필드(예: status)를 두고, 이에 따라 타입 추론이 자동으로 되도록 만드는 거지.

type ApiResponse<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; message: string };

이렇게 해두면 status'success'일 때는 data에 안전하게 접근할 수 있고, 'error'일 때는 message만 자동 완성되거든. 굳이 불필요한 if문 도배를 안 해도 되니까 코드가 훨씬 안전해져.

2. 문자열은 '템플릿 리터럴 타입(Template Literal Types)'으로 묶어줘

CSS-in-JS를 쓰거나, 특정 API 라우트 경로를 다룰 때 그냥 string 타입을 지정하는 경우가 많지? 하지만 이러면 오타가 나도 런타임 에러가 나기 전까지는 알 방법이 없어. 타입스크립트의 템플릿 리터럴 타입을 활용하면 문자열의 패턴까지 타입으로 정의할 수 있어. 예를 들어, 특정 단위가 붙어야 하는 CSS 속성이나 특정 접두사가 붙어야 하는 이벤트명을 제한할 때 아주 유용해.

type EventName = `on${Capitalize<string>}`;
type Padding = `${number}px` | `${number}rem`;

이렇게 정의해두면 실수로 onclickonclik으로 쓰거나, 10px 대신 10p로 쓰는 실수를 컴파일 시점에 완전히 차단할 수 있거든.

web design coding

3. '조건부 타입(Conditional Types)'과 'infer'로 유연성 극대화하기

라이브러리를 만들거나, 공통 컴포넌트를 설계할 때 타입의 유연성이 절실할 때가 있어. 이때 유용하게 쓰이는 게 바로 extends를 활용한 조건부 타입infer 키워드야. infer는 타입스크립트가 런타임의 타입을 스스로 추론하도록 만드는 마법 같은 도구야. 예를 들어, 특정 함수의 반환 타입을 동적으로 추출하고 싶을 때 직접 타입을 하드코딩하지 않고도 아래처럼 우아하게 해결할 수 있어.

type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

이 패턴을 잘 쓰면 복잡한 API 응답 구조나 외부 라이브러리의 타입을 가공해서 우리 프로젝트에 딱 맞게 재사용할 수 있게 돼. 타입 중복 선언을 획기적으로 줄여주니까 꼭 익혀두길 바래.

💡 핵심 정리

  • 판별 가능한 유니온: API 응답이나 상태 관리를 할 때 분기 처리를 안전하게 만들어줌.
  • 템플릿 리터럴 타입: 무분별한 string 사용을 막고, 문자열 패턴을 컴파일 단계에서 검증함.
  • 조건부 타입과 infer: 타입의 중복을 줄이고, 동적인 타입 추론으로 코드의 유연성을 극대화함. 처음에는 타입 정의하는 게 귀찮고 시간 낭비처럼 느껴질 수 있어. 하지만 잘 짜인 타입 하나가 백 장의 API 문서보다 낫고, 수많은 런타임 버그를 미리 막아준다는 걸 기억해줘. 오늘 알려준 패턴들을 하나씩 네 코드에 적용해보면서 타입스크립트의 진짜 재미를 느껴봤으면 좋겠다!