타입 가드는 TypeScript가 이해하는 런타임 검사로, 검사한 분기 안에서 타입을 좁힙니다. typeof, instanceof, in은 내장 가드이고, 그 밖의 경우에는 반환 타입이 타입 서술어 value is Type인 함수를 작성합니다.
isUser는 런타임에 평범한 불리언을 반환합니다. 반환 타입 value is User는 true 결과가 무엇을 증명하는지 컴파일러에 알려 주고, 그러면 모든 if (isUser(x))가 x를 User로 좁힙니다.
내장 타입 가드
다음 검사는 헬퍼 함수 없이 좁힙니다.
| 가드 | 예 | 쓰는 곳 |
|---|---|---|
typeof | typeof x === "number" | 원시 타입과 함수 |
instanceof | x instanceof Date | 클래스 인스턴스 |
in | "email" in x | 객체 유니언, unknown 객체의 속성 |
Array.isArray | Array.isArray(x) | 배열 |
| 동등 비교 | x === null, x.kind === "circle" | null/undefined, 리터럴 태그 |
| 참 같은 값 | if (x) | null과 undefined 제거 |
모두 평범한 JavaScript로 실행됩니다. TypeScript가 더하는 것은 좁히기입니다. 검사를 읽고 각 분기의 타입을 조정합니다. 전체 형태 목록은 타입 좁히기 페이지에 있습니다. 직접 만드는 가드는 표현식 하나에 담기지 않거나 재사용하고 싶은 검사를 위한 것입니다.
타입 서술어 작성하기
타입 서술어는 parameterName is Type 형태이며 반환 타입 boolean을 대신합니다. 좁히기는 양방향으로 동작합니다. true는 Type으로 좁히고, false는 유니언에서 Type을 없앱니다.
가드를 filter에 넘기면 타입이 올바른 배열을 얻습니다. TypeScript 5.5부터는 컴파일러가 단순한 화살표 함수에서도 서술어를 추론하므로, 이름 있는 가드 없이도 pets.filter((p) => p.kind === "cat")가 Cat[]를 반환합니다.
서술어의 타입은 매개변수의 타입에 맞아야 합니다. function f(x: string): x is number는 오류 TS2677, A type predicate's type must be assignable to its parameter's type.입니다.
컴파일러는 가드를 믿는다
TypeScript는 가드가 불리언을 반환하는지 검사합니다. 그 불리언이 옳은지는 검사하지 않습니다. 잘못된 값에 true를 반환하는 가드는 타입을 거짓말하게 만들고, 프로그램은 컴파일 오류 없이 런타임에 실패합니다.
data.price.toFixed(2)는 런타임에 TypeError: Cannot read properties of undefined (reading 'toFixed')를 던집니다. 가드가 그렇다고 했으므로 컴파일러는 data.price를 number로 받아들였습니다. 나머지 코드가 기대는 모든 속성을 확인하고, 가드는 작게, 테스트를 거쳐, 그것이 기술하는 타입 가까이에 두세요.
객체가 어떤 타입인지 확인하기
대부분의 직접 만든 가드 뒤에 있는 질문입니다. 데이터가 unknown으로 도착하고(JSON.parse, fetch, localStorage, 메시지에서) 그것이 인터페이스에 맞는지 알아야 합니다. 방법은 이렇습니다.
typeof value === "object" && value !== null(null이 아닌 객체).- 필수 속성마다
"prop" in value.unknown에 대해in은 그 속성을unknown타입으로 타입에 추가합니다. - 속성마다 그 타입에 대한
typeof value.prop === "..."(또는 중첩 가드). - 배열에는
Array.isArray(value.items) && value.items.every(isItem).
모양이 크거나 깊이 중첩되어 있으면 이것을 손으로 쓰기가 번거로워집니다. Zod나 Valibot 같은 스키마 라이브러리를 쓰면 모양을 한 번 기술하고 런타임 검사와 TypeScript 타입을 둘 다 얻을 수 있습니다.
단언 함수: asserts value is Type
단언 함수는 검사가 실패하면 예외를 던지고, 그렇지 않으면 정상적으로 반환합니다. 반환 타입은 asserts value is Type(또는 asserts condition)이며, if 없이도 호출 뒤의 모든 곳이 좁혀집니다.
사람들이 걸려 넘어지는 규칙이 하나 있습니다. 단언 함수는 명시적 타입이 있는 이름을 통해 호출해야 합니다. 표기 없는 const 화살표 함수 const check = (v: unknown): asserts v is string => {...}는 호출한 곳에서 오류 TS2775, Assertions require every name in the call target to be declared with an explicit type annotation.를 냅니다. function 선언을 쓰거나, 상수에 함수 타입을 표기하세요.
가드, 단언, 캐스트 비교
| 도구 | 런타임 검사? | 좁히는 범위 | 실패하면 |
|---|---|---|---|
내장 가드(typeof, in...) | 예 | 분기 안 | 다른 분기로 감 |
value is T 함수 | 예(직접 작성한 코드) | 분기 안 | 다른 분기로 감 |
asserts value is T 함수 | 예(직접 작성한 코드) | 호출 뒤 | 예외를 던짐 |
value as T | 아니요 | 그 표현식 | 아무 일도 없음: 잘못된 타입이 퍼짐 |
타입 단언(as)은 아무것도 검사하지 않고 타입을 바꿉니다. 외부에서 데이터가 들어오는 경계에서는 가드나 단언 함수가 같은 생각의 안전한 버전입니다.
클래스의 this 기반 가드
메서드는 this is Type으로 자신이 호출된 객체를 좁힐 수 있습니다. 클래스 계층에서 편리합니다.
class FileNode {
constructor(public name: string) {}
isDirectory(): this is DirectoryNode {
return this instanceof DirectoryNode;
}
}
class DirectoryNode extends FileNode {
children: FileNode[] = [];
}
function count(node: FileNode): number {
return node.isDirectory() ? node.children.length : 0; // node: DirectoryNode in the true branch
}
자주 묻는 질문
TypeScript에서 타입 가드란 무엇인가요?
TypeScript가 타입을 좁히는 데 쓰는 런타임 검사라면 무엇이든 타입 가드입니다: typeof x === "string", x instanceof Date, "id" in x, Array.isArray(x), 또는 반환 타입이 x is User 같은 타입 서술어인 함수의 호출. 검사한 분기 안에서 변수는 더 좁은 타입을 갖습니다.
TypeScript에서 객체가 어떤 타입인지 어떻게 확인하나요?
타입은 런타임에 존재하지 않으므로 속성을 확인합니다. typeof value === "object", value !== null, 그리고 필수 속성 각각을 in과 typeof로 검사하는 함수 isUser(value: unknown): value is User를 작성하세요. if (isUser(x)) 뒤에 x는 User 타입입니다. 클래스라면 x instanceof MyClass로 충분합니다.
TypeScript에서 "value is Type"은 무슨 뜻인가요?
함수의 반환 타입으로 쓰는 타입 서술어입니다. 함수는 런타임에 여전히 불리언을 반환하지만, true를 반환하면 TypeScript가 호출한 곳에서 인수를 Type으로 좁히고, false를 반환하면 유니언의 나머지 멤버로 좁힙니다. 컴파일러는 함수 본문을 검증하지 않으므로 검사가 정확해야 합니다.
타입 가드와 단언 함수의 차이는 무엇인가요?
타입 가드(x is T)는 불리언을 반환하고 if 안에서 좁힙니다. 단언 함수(asserts x is T)는 아무것도 반환하지 않고 검사가 실패하면 예외를 던지므로, if 없이도 호출 뒤의 모든 곳이 좁혀집니다. 분기에는 가드를, "반드시 성립해야 하고 아니면 멈춘다"에는 단언을 쓰세요.
TypeScript에서 객체가 인터페이스를 구현하는지 확인할 수 있나요?
직접은 안 됩니다. 인터페이스는 지워지고 instanceof는 인터페이스를 받지 않습니다. 인터페이스의 속성을 확인하는 타입 가드를 작성하거나, 리터럴 태그 속성(kind: "user")을 추가해 비교하세요. Zod 같은 스키마 라이브러리는 정의 하나에서 검사와 타입을 모두 만들어 줍니다.