Ch 01. typeof와 instanceof
TypeScript는 if 블록 안의 typeof 검사를 보고 해당 블록에서 타입을 자동으로 좁혀줍니다. 이것을 제어 흐름 분석(control flow analysis)이라고 합니다. 직접 타입을 선언하지 않아도 컴파일러가 맥락을 추론해 주는 TypeScript만의 강점입니다.
typeof로 원시 타입 가드 작성하기
유니온 타입을 받는 함수를 생각해 봅니다. 인자가 string일 때와 number일 때 다르게 처리해야 한다면 typeof가 가장 단순한 해결책입니다.
// 파일: src/guards/typeof-guard.tsfunction formatValue(value: string | number): string { if (typeof value === "string") { // 이 블록 안에서 value의 타입은 string return value.toUpperCase(); } // 이 블록에서는 number return value.toFixed(2);}console.log(formatValue("hello")); // "HELLO"console.log(formatValue(3.14159)); // "3.14"
typeof가 반환할 수 있는 값은 "string", "number", "boolean", "bigint", "symbol", "undefined", "object", "function" 여덟 가지입니다. null을 확인할 때 typeof null === "object"가 true가 되는 것은 JavaScript의 오래된 버그입니다. null은 별도로 검사해야 합니다.
// 파일: src/guards/null-guard.tsfunction getLength(value: string | null | undefined): number { if (value == null) { // == null은 null과 undefined 모두 잡습니다 return 0; } // 여기서 value는 string return value.length;}
제어 흐름 분석이 동작하는 방식
TypeScript 컴파일러는 코드 경로를 따라가며 각 지점에서 변수의 타입을 추적합니다. if, else, switch, 삼항 연산자, 논리 연산자 등 제어 흐름에 영향을 주는 모든 구문을 이해합니다.
// 파일: src/guards/control-flow.tsfunction processInput(input: string | number | boolean) { if (typeof input === "string") { // string console.log(input.split("")); } else if (typeof input === "number") { // number console.log(input * 2); } else { // boolean — 다른 경우의 수가 없으므로 TypeScript가 boolean으로 좁힙니다 console.log(input ? "yes" : "no"); }}
early return 패턴도 동일하게 동작합니다. 함수 중간에서 return하면 이후 코드에서 해당 타입은 제거됩니다.
// 파일: src/guards/early-return.tsfunction processString(value: string | null): string { if (value === null) { return "기본값"; } // 여기서 value는 반드시 string — null은 이미 처리됨 return value.trim();}
instanceof로 클래스 인스턴스 검사하기
instanceof는 클래스 계층에서 특히 유용합니다. 에러 처리에서 자주 볼 수 있는 패턴입니다.
// 파일: src/guards/instanceof-guard.tsclass NetworkError extends Error { constructor( public statusCode: number, message: string ) { super(message); this.name = "NetworkError"; }}class ValidationError extends Error { constructor( public field: string, message: string ) { super(message); this.name = "ValidationError"; }}function handleError(error: unknown): string { if (error instanceof NetworkError) { // NetworkError 타입으로 좁혀져서 statusCode에 접근 가능 return `네트워크 오류 ${error.statusCode}: ${error.message}`; } if (error instanceof ValidationError) { // ValidationError 타입으로 좁혀져서 field에 접근 가능 return `검증 오류 (${error.field}): ${error.message}`; } if (error instanceof Error) { return `일반 오류: ${error.message}`; } return "알 수 없는 오류";}
instanceof는 클래스뿐 아니라 Date, RegExp, Array 같은 내장 타입에도 사용할 수 있습니다.
// 파일: src/guards/instanceof-builtin.tsfunction formatDate(value: Date | string): string { if (value instanceof Date) { return value.toISOString().split("T")[0]; } // string return new Date(value).toISOString().split("T")[0];}function flatten<T>(value: T | T[]): T[] { if (value instanceof Array) { return value; } return [value];}
typeof vs instanceof 선택 기준
- 원시 타입(string, number, boolean 등)을 구분할 때는 typeof
- 클래스 인스턴스나 내장 객체(Date, RegExp, Array 등)를 구분할 때는 instanceof
- 일반 객체 리터럴을 구분할 때는 다음 챕터에서 다루는 판별 유니온이나 사용자 정의 타입 가드를 사용합니다
instanceof의 한계는 인터페이스에는 사용할 수 없다는 점입니다. 인터페이스는 컴파일 후 사라지기 때문에 런타임에 확인할 수 없습니다. 그 경우 Ch 03의 사용자 정의 타입 가드를 사용합니다.
실습: 계산기 함수
typeof와 instanceof를 함께 사용하는 예제입니다.
// 파일: src/guards/calculator.tstype Operand = number | string | { value: number };function toNumber(operand: Operand): number { if (typeof operand === "number") { return operand; } if (typeof operand === "string") { const parsed = parseFloat(operand); if (isNaN(parsed)) { throw new Error(`숫자로 변환할 수 없습니다: ${operand}`); } return parsed; } // 객체 — { value: number } return operand.value;}function add(a: Operand, b: Operand): number { return toNumber(a) + toNumber(b);}console.log(add(1, 2)); // 3console.log(add("3.5", 1.5)); // 5console.log(add({ value: 10 }, 5)); // 15
typeof와 instanceof는 TypeScript 타입 가드의 시작입니다. 두 연산자만으로 대부분의 기본 분기 처리를 커버할 수 있습니다. 더 복잡한 유니온 타입을 다루려면 다음 챕터의 판별 유니온이 필요합니다.