노트

조건부 타입과 infer

Conditional Types

프런트엔드#ts · 연결된 개념 6개

쉽게 말하면

조건부 타입은 타입에 거는 if-else예요. '프로미스면 안에 든 걸 꺼내고 아니면 그대로' 같은 규칙을 적어 두면, 함수가 바뀔 때 거기서 끌어온 타입도 알아서 따라 바뀌니 같은 타입을 두 번 안 써도 돼요.

비유가 깨지는 곳 if-else를 겹겹이 쌓을수록 좋은 건 아니에요. 한 줄짜리 타입 퍼즐은 팀원이 못 읽고 에러 메시지도 난해해지니, 실수를 막고 변경을 안전하게 하는 만큼만 써요.

조건부 타입(Conditional Types)은 T extends U ? X : Y 꼴로, 타입 수준에서 if-else를 하는 문법이다. infer는 조건부 타입 안에서 타입의 일부를 변수로 붙잡아 꺼낼 때 쓴다.

type IsString<T> = T extends string ? true : false
type ElementType<T> = T extends (infer U)[] ? U : never
type Unwrap<T> = T extends Promise<infer U> ? U : T
 
async function fetchUser() { return { id: 'u1', name: 'Kim' } }
type User = Unwrap<ReturnType<typeof fetchUser>>  // { id: string; name: string }
  • 함수 반환 타입에서 도메인 타입을 끌어오면 함수가 바뀔 때 타입이 같이 따라온다. 같은 타입을 두 번 선언하지 않는다
  • 입력 타입에 따라 반환 타입을 바꾸는 API(Application Programming Interface)를 오버로드 없이 표현할 수 있다(함수 오버로드와 시그니처)
  • 내장 ReturnType, Parameters, Awaited, Exclude가 모두 이 방식으로 만들어졌다(유틸리티 타입)
  • 과하면 독이다. 한 줄짜리 타입 퍼즐은 팀원이 읽지 못하고 에러 메시지도 난해해진다. 고급 타입의 목적은 똑똑해 보이는 것이 아니라 실수를 막고 변경을 안전하게 하는 것이다(KISS 원칙)

키를 돌며 바꾸는 쪽은 매핑된 타입과 keyof, 기초는 제네릭.

연결된 개념

이 노트를 가리키는 문서

뜻이 가까운 노트

  • 타입 추론과 타입 검사

    타입 추론(Type Inference)은 표기가 없어도 컴파일러가 값의 타입을 정하는 단계이고, 타입 검사(Type Checking)는 정해진(추론됐거나 명시된) 타입을 코드가 지키는지 확인하는 단계다. 보통 추론이 먼저 일어나고, 그 결과를 기준으로 검사한다.

  • 조건부 로직을 다형성으로 바꾸기

    타입에 따라 갈라지는 조건문을, 타입마다 클래스(또는 객체)를 두고 각자 자기 방식으로 처리하게 바꾸는 리팩터링.

  • 기본형 집착

    전화번호·금액·우선순위 같은 도메인 개념을 끝까지 문자열이나 숫자로만 다루는 냄새. 같은 검증과 포맷 코드가 여기저기 반복되고, 문자열로 모든 걸 표현하는 "stringly typed" 코드가 된다.

  • type과 interface

    type(타입 별칭, Type Alias)과 interface는 둘 다 타입에 이름을 붙이는 방법이다. 객체 모양을 정의할 때는 대부분 바꿔 써도 되지만, 확장 방식과 표현 범위가 다르다.

  • 보호 구문

    정상 흐름이 아닌 경우를 함수 앞부분에서 검사하고 바로 빠져나오는 조건문. 중첩된 if-else를 평평하게 펴서 핵심 흐름을 드러낸다.

보기 옵션