본문 바로가기
C.W.K.
Stream
Lesson 04 of 05 · published

타입 술어: 복합 검증을 재사용 가능한 좁히기로

~11 min · narrowing, type-predicates, type-guards, user-defined

Level 0Curious
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete
"기본 연산 하나로 설명할 수 없는 검증이라면, 참일 때 어떤 타입인지 함수 계약에 적어."

반환 타입의 특별한 문법

function isUser(x: unknown): x is User처럼 반환 타입에 x is User를 쓰면 타입 술어가 돼. 함수 몸은 평범한 boolean을 반환하지만, 호출 결과가 참인 분기에서는 호출자의 x가 User로 좁혀진다.

속성 여러 개의 존재와 값 타입을 함께 확인하거나 중첩된 JSON 구조를 검증할 때 유용해. 긴 검사 코드를 한 함수에 모으고, 사용하는 곳에서는 내장 좁히기처럼 읽을 수 있다.

컴파일러는 술어의 말을 믿어

중요한 함정은 TypeScript가 함수 몸이 반환 타입의 약속을 실제로 증명하는지 확인하지 못한다는 점이야. 무조건 true를 반환해도 x is User라고 적을 수 있어. 술어가 거짓말하면 그 뒤의 타입 안전도 함께 무너진다.

그래서 타입 술어는 신뢰 경계의 코드로 다뤄. 정상값뿐 아니라 빠진 속성, null, 잘못된 원시값, 중첩 오류를 테스트해. 외부 입력 검증이 커지면 Zod나 Valibot 같은 스키마 도구가 파싱과 타입 추론을 함께 관리하는 편이 낫다.

직접 술어를 쓸 자리

  • JSON이나 사용자 입력처럼 타입 없는 외부 데이터를 검증할 때
  • 공통 판별 필드가 없는 객체 유니언의 모양을 검사할 때
  • 하나의 typeof나 instanceof로 표현하기 어려운 검사를 묶을 때

Zod, Valibot, io-ts 같은 라이브러리는 이 경계 검사 패턴을 파서와 타입 추론으로 확장해.

타입 술어는 컴파일러가 믿는 증언이야. 경계 검증처럼 테스트하고, 작고 명확한 증거만 약속해.

Code

커스텀 isUser predicate·typescript
interface User { name: string; email: string }

// Type predicate — return type 'x is User' 주목.
function isUser(x: unknown): x is User {
  return (
    typeof x === 'object' && x !== null &&
    'name' in x && typeof (x as any).name === 'string' &&
    'email' in x && typeof (x as any).email === 'string'
  );
}

// 다른 narrowing 체크처럼 써.
function handle(input: unknown) {
  if (isUser(input)) {
    // 여기서 input 이 User 로 narrow.
    return `Hello, ${input.name}`;
  }
  return 'Invalid input';
}

// Predicate 없으면 property 체크 inline 으로 narrow — 길고,
// 재사용 불가능, 끝에 User-typed 접근 잃어.
조합 가능한 predicate 와 Array.isArray·typescript
// 배열-specific predicate — Array.isArray 가 built-in type predicate.
function processItems(x: unknown) {
  if (Array.isArray(x)) {
    // x 가 any[] 로 narrow (built-in predicate)
    return x.length;
  }
  return 0;
}

// 중첩 구조에 predicate compose 가능.
function isStringArray(x: unknown): x is string[] {
  return Array.isArray(x) && x.every((v) => typeof v === 'string');
}

function collect(input: unknown) {
  if (isStringArray(input)) {
    // input: string[]
    return input.join(', ');
  }
  return '';
}

External links

Exercise

status가 숫자이고 data 키가 있는지 확인하는 isApiResponse 타입 술어를 작성해. 이어서 fetch().then(r => r.json())의 결과를 받는 함수에서 이 술어를 사용해 data에 안전하게 접근해.
Hint
값이 객체이고 null이 아닌지 먼저 확인해. status가 숫자이며 data 키가 있는지도 검사하되, data 자체는 unknown으로 남겨.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.