타입스크립트 교재 · 14편 / 20편

타입 가드 — typeof·instanceof·is·asserts

유니온을 좁히는 5가지 도구. unknown 데이터를 안전하게 다루기.

중급읽는 시간 7분2026-05-17
유니온 타입이 typeof/instanceof/is 검사를 거쳐 좁아지는 흐름

5편의 narrowing 을 깊게 — 유니온 타입을 좁히는 도구 5가지를 정리합니다. typeof·instanceof·in 같은 내장 도구 외에도 사용자가 만드는 타입 술어(is) 와 어서션 함수(asserts) 까지. unknown 으로 들어온 외부 데이터를 안전하게 다루는 핵심.

typeof — primitive 좁히기

function format(x: string | number): string {
  if (typeof x === "string") {
    return x.toUpperCase();    // x: string
  }
  return x.toFixed(2);         // x: number
}

// typeof 결과 7가지
typeof "x"     // "string"
typeof 1       // "number"
typeof true    // "boolean"
typeof undef   // "undefined"
typeof null    // "object"     ← 함정! (4편)
typeof []      // "object"
typeof {}      // "object"
typeof fn      // "function"
typeof Symbol() // "symbol"
typeof 1n      // "bigint"

instanceof — 클래스/생성자 좁히기

try {
  doStuff();
} catch (err) {
  if (err instanceof TypeError) {
    console.error("타입 에러:", err.message);
  } else if (err instanceof RangeError) {
    console.error("범위 에러");
  } else if (err instanceof Error) {
    console.error("기타 에러:", err.message);
  } else {
    throw err;
  }
}

// 사용자 클래스도
class User { constructor(public name: string) {} }
class Admin extends User { constructor(name: string, public level: number) { super(name); } }

function greet(u: User | Admin) {
  if (u instanceof Admin) {
    console.log(`관리자 ${u.name} (lv${u.level})`);
  } else {
    console.log(`사용자 ${u.name}`);
  }
}

in — 속성 유무로 좁히기

type Cat = { meow(): void };
type Dog = { bark(): void };

function speak(pet: Cat | Dog) {
  if ("meow" in pet) {
    pet.meow();    // pet: Cat
  } else {
    pet.bark();    // pet: Dog
  }
}

// 옵션 속성 검사
type Config = { timeout?: number };
function call(c: Config) {
  if ("timeout" in c && c.timeout !== undefined) {
    setTimeout(..., c.timeout);
  }
}

리터럴 비교 — 구분 가능 유니온 (5편 다시)

type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function area(s: Shape): number {
  if (s.kind === "circle") {
    return Math.PI * s.radius ** 2;   // s: circle
  }
  return s.side ** 2;                  // s: square
}

가장 자주 쓰는 narrowing. kind 같은 literal 필드 한 줄 비교로 나머지 모양이 결정됨.

사용자 정의 타입 가드 — is 키워드

// 일반 boolean 반환 함수는 narrowing 효과 없음
function isUser(x: unknown): boolean {
  return typeof x === "object" && x !== null && "id" in x;
}

let data: unknown;
if (isUser(data)) {
  data.id;   // ❌ unknown 그대로
}

// is 키워드로 "타입 술어" 명시
function isUser(x: unknown): x is { id: number; name: string } {
  return typeof x === "object" && x !== null
    && "id" in x && typeof (x as any).id === "number";
}

if (isUser(data)) {
  data.id;     // OK — id: number
  data.name;   // OK — name: string
}

is 키워드 = 약속. "이 함수가 true 를 반환하면 인자는 이 타입" 이라고 TS 에게 약속합니다. 본문 구현이 약속을 안 지키면 TS 가 못 잡습니다 — 런타임 에러로 이어지므로 검사 로직을 신중하게.

asserts — 어서션 함수

// 통과하면 그 타입이 보장됨, 안 맞으면 throw
function assertString(x: unknown): asserts x is string {
  if (typeof x !== "string") {
    throw new Error(`expected string, got ${typeof x}`);
  }
}

let v: unknown;
assertString(v);
v.toUpperCase();   // OK — 여기 도달했다면 string 보장

// 표준 라이브러리 비슷 — Node 의 assert
import assert from "node:assert/strict";
function process(x: number | undefined) {
  assert(x !== undefined, "x must be defined");
  x.toFixed(2);    // OK
}

안전한 외부 데이터 처리 — zod 패턴

// fetch 응답을 타입 안전하게
async function loadUser(id: number): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  const raw: unknown = await res.json();

  if (!isUser(raw)) {
    throw new Error("API 응답 형식 이상");
  }

  return raw;
}

// 실전에서는 zod·yup·io-ts 같은 라이브러리 (런타임 + 타입 동시)
import { z } from "zod";
const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email().optional(),
});
type User = z.infer<typeof UserSchema>;

const raw = await res.json();
const user = UserSchema.parse(raw);   // throw if invalid + 타입 보장

현대 표준. 외부 입력(JSON·query string·env) 은 zod 같은 스키마 라이브러리로 한 번에 검증 + 타입. 직접 is 가드 쓰는 것보다 안전하고 변경 추적도 쉽습니다.

흐름 분석의 한계 — narrowing 깨지는 경우

function fn(obj: { x?: number }) {
  if (obj.x !== undefined) {
    helper();        // 함수 호출 — TS 는 obj.x 가 그대로인지 모름
    obj.x.toFixed(2); // ❌ obj.x 가 다시 undefined 가능성으로 되돌아감 (보수적)
  }

  // 로컬 변수에 담아두면 narrowing 유지
  const x = obj.x;
  if (x !== undefined) {
    helper();
    x.toFixed(2);    // OK
  }
}

객체 속성 narrowing 은 함수 호출이 끼면 풀립니다 — 그 함수가 객체를 바꿨을 수 있으니까요. 로컬 변수에 담아 narrow 하는 게 안전한 패턴.

한 표로 — 타입 가드 5종

도구주 용도
typeofprimitive (string·number 등)
instanceof클래스 인스턴스
in속성 유무
=== literal구분 가능 유니온 (kind/type 필드)
x is T (술어)복잡한 객체 모양 검증
asserts x is T검증 실패 시 throw, 통과 시 타입 보장

15편 — 비동기 + Promise<T> 다루기

Promise·async 반환 타입, fetch 결과 타이핑, catch 에서 unknown.

📚 쉽게 배우는 타입스크립트 교재
이전: 13편 매핑 타입 · 현재: 14편 (중급) · 다음 → 15편 비동기 타입 · 진행: 14/20

© 2026 주나이테크(주) @JUNAITECH