숏컷 코드
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id.toFixed(0));
}
}좁히는 흐름
좁히기에서 먼저 자주 쓰는 형태는 아래 네 가지입니다.
typeof value === "string"value instanceof Date"prop" in valuefunction isX(v): v is Xswitch (value.kind)같은 discriminated union 분기
1) 원시 타입 분기면 typeof
문자열, 숫자, 불리언처럼 원시 타입을 좁힐 때는 typeof가 가장 먼저입니다.
function read(input: string | string[]) {
if (typeof input === "string") {
return input.toUpperCase();
}
return input.join(", ");
}2) 클래스 인스턴스 분기면 instanceof
Date 같은 클래스 인스턴스 여부를 기준으로 로직이 갈리면 instanceof가 자연스럽습니다.
function format(value: string | Date) {
if (value instanceof Date) {
return value.toISOString();
}
return value.trim();
}3) 구조 차이로 분기하면 in
객체 union에서 특정 프로퍼티 존재 여부가 케이스를 가르면 in이 기본 도구입니다.
type Cat = { meow(): void };
type Dog = { bark(): void };
function speak(animal: Cat | Dog) {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}이 방식은 같은 필드를 직접 읽으려다 에러가 나는 상황을 막는 데 특히 유용합니다.
4) 반복되는 확인 로직은 사용자 정의 타입 가드로 뺀다
같은 narrowing 패턴이 여러 군데 반복되면 value is T 형태의 함수로 분리할 수 있습니다.
interface Cat { meow(): void; }
interface Dog { bark(): void; }
function isCat(animal: Cat | Dog): animal is Cat {
return "meow" in animal;
}이 패턴은 런타임 확인 로직과 컴파일타임 타입 전달을 한 번에 맡습니다.
5) 태그 필드가 있으면 switch 분기가 더 읽기 좋다
union 멤버마다 kind, type, status 같은 공통 태그 필드가 있으면 switch로 분기하는 편이 더 읽기 좋은 경우가 많습니다.
type Shape =
| { kind: "circle"; r: number }
| { kind: "rect"; w: number; h: number };
function area(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.r ** 2;
case "rect":
return shape.w * shape.h;
}
}6) as로 덮는 대신 실제 분기를 우선한다
타입 단언은 필요할 때 쓰는 도구지만, narrowing이 가능한 상황에서 습관적으로 as를 쓰면 TypeScript의 장점이 줄어듭니다.
가능한 한 실제 조건으로 타입을 좁히는 편이 더 안전합니다.
언제 어떤 가드를 쓸까
좁히기를 할 때 볼 점
- 원시 타입 분기다:
typeof - 클래스 인스턴스 분기다:
instanceof - 프로퍼티 존재 여부로 분기한다:
in - 같은 확인 로직이 반복된다: 사용자 정의 타입 가드
- 단언으로 덮고 싶어진다: 먼저 narrowing 가능한지 확인
주의할 점
타입 단언(as)으로 바로 덮어쓰면 narrowing의 이점을 잃기 쉽습니다. 가능한 한 실제 분기 조건으로 좁히는 편이 더 안전합니다.
// ❌ 확인 없이 단언으로 덮음
function upper(value: string | number) {
return (value as string).toUpperCase();
}
// ✅ 실제 분기로 좁힘
function upper(value: string | number) {
return typeof value === "string" ? value.toUpperCase() : String(value);
}
// ❌ 객체 union인데 확인 없이 속성 접근
function bark(animal: Cat | Dog) {
return animal.bark();
}
// ✅ 프로퍼티 유무로 좁힘
function bark(animal: Cat | Dog) {
return "bark" in animal ? animal.bark() : undefined;
}참고 링크
1 sources