타입 가드(Type Guard)
들어가며
- 타입스크립트(TypeScript)의 타입 가드(Type Guard)에 대해 정리해본다.

타입 가드(Type Guard)
개념
- 타입스크립트에서 변수의 타입을 좁히는 방법
- 주로 조건문(Conditional Statement)을 사용하여 특정 타입임을 확인한 후, 해당 타입에 맞는 안전한 작업을 수행할 수 있도록 도와준다.
- 타입 가드를 사용하면 컴파일 시점에 타입 오류를 방지하고, 코드의 가독성과 안정성을 높일 수 있다.
- 타입 가드를 효과적으로 사용하면 런타임 오류를 줄이고, 개발 과정에서 타입 관련 버그를 미리 방지할 수 있으며, 코드의 의도를 명확하게 표현하여 협업 시 가독성을 높이는 데 큰 도움이 된다.
타입 가드는 런타임에서 변수의 타입을 확인하여 타입스크립트에게 해당 변수의 타입을 좁히도록 지시하는 역할을 한다.
방법
① typeof 연산자 이용하기
typeof
연산자를 이용하는 방법이다.typeof
연산자는 변수의 기본 타입을 확인할 때 사용된다.
- 주로
string
,number
,boolean
과 같은 원시 타입(Primitive Type)을 구분할 때 유용하다.
예제 코드
type ValueType = string | number | boolean; let value: ValueType; const random = Math.random(); value = random < 0.33 ? 'Hello' : random < 0.66 ? 123.456 : true; function checkValue(value: ValueType) { if (typeof value === 'string') { console.log(value.toLowerCase()); return; } if (typeof value === 'number') { console.log(value.toFixed(2)); return; } console.log(`boolean: ${value}`); } checkValue(value);
⇒ checkValue
함수는 ValueType
타입의 매개변수를 받는다.
⇒ typeof
를 사용하여 value
의 타입을 검사하고, 각 타입에 맞는 처리를 수행한다.
② 동등성 좁히기(Equality Narrowing)
- 동등성 검사(
===
,!==
)를 사용하여 변수의 타입을 좁히는 방법 - 주로 유니온(Union) 타입에서 특정 리터럴 값으로 타입을 구분할 때 유용하다.
예제 코드
type Dog = { type: 'dog'; name: string; bark: () => void }; type Cat = { type: 'cat'; name: string; meow: () => void }; type Animal = Dog | Cat; function makeSound(animal: Animal) { if (animal.type === 'dog') { animal.bark(); } else { animal.meow(); } }
⇒ Animal
타입은 Dog
또는 Cat
일 수 있다.
⇒ animal.type
을 비교하여 Dog
인지 Cat
인지 구분하고, 각각의 메서드를 호출한다.
③ in 연산자 이용하기
in
연산자를 이용하는 방법이다.in
연산자를 이용하여 객체에 특정 속성이 있는지 확인함으로써 타입을 좁힐 수 있다.
예제 코드
type Dog = { type: 'dog'; name: string; bark: () => void }; type Cat = { type: 'cat'; name: string; meow: () => void }; type Animal = Dog | Cat; function makeSound(animal: Animal) { if ('bark' in animal) { animal.bark(); } else { animal.meow(); } }
⇒ bark
메서드가 animal
객체에 있는지 확인하여 Dog
인지 Cat
인지 구분한다.
④ Truthy/Falsy 값 이용하기
- Truthy/Falsy 값을 이용하여 변수의 타입을 좁히는 방법이다.
- 주로
null
,undefined
, 빈 문자열 등을 확인할 때 사용된다.
(참고) Truthy한 값과 Falsy한 값
Truthy한 값 | Falsy한 값 |
true , 1 , 'hello' (빈 문자열이 아닌 모든 문자열), [] (빈 배열), {} (빈 객체), Infinity (양/음의 무한대) |
false , 0 , "" , '' , null , undefined , NaN (Not-a-Number) |
예제 코드
function printLength(str: string | null | undefined) { if (str) { console.log(str.length); } else { console.log('No string provided'); } } printLength('Hello'); // 5 printLength(null); // No string provided printLength(undefined); // No string provided
⇒ str
이 Truthy 값인 경우 문자열의 길이를 출력하고, 그렇지 않으면 'No string provided'
를 출력한다.
⑤ instanceof 연산자 이용하기
instanceof
연산자를 사용하여 객체가 특정 클래스의 인스턴스인지 확인한다.- 주로 클래스 기반 객체를 구분할 때 유용하다.
예제 코드
function checkInput(input: Date | string): string { if (input instanceof Date) { return input.getFullYear().toString(); } return input; } const year = checkInput(new Date()); const random = checkInput('2020-05-05'); console.log(year); // 현재 연도 console.log(random); // '2020-05-05'
⇒ input
이 Date
객체인지 확인하고, 맞다면 연도를 문자열로 반환한다.
⇒ 그렇지 않으면 원래 문자열을 반환한다.
⑥ 타입 프리디케이트(Type Predicate) 이용하기
- 타입 프레디케이트(Type Predicate)는 함수의 반환(return) 타입이 특정 타입임을 명시하여 타입을 좁히는 방법이다.
- 주로 사용자 정의 타입 가드를 만들 때 사용된다.
is
연산자를 이용한다.is
연산자는 해당 변수가 특정 타입인지 확인해준다.
예제 코드
type Student = { name: string; study: () => void; }; type User = { name: string; login: () => void; }; type Person = Student | User; // 타입의 반환 타입이 특정 타입임을 명시하기 (person is Student) function isStudent(person: Person): person is Student { return (person as Student).study !== undefined; } const person: Person = { name: 'anna', study: () => console.log('Studying'), }; if (isStudent(person)) { person.study(); // Student 타입으로 좁혀짐 } else { person.login(); }
⇒ isStudent
함수는 Person
타입의 매개변수를 받아 Student
타입인지 확인한다.
⇒ 타입 프레디케이트(person is Student
)를 사용하여 타입을 좁힌다.
⇒ if
블록에서는 person
이 Student
임을 타입스크립트가 인식하여 study
메서드를 안전하게 호출할 수 있다.
⑦ 사용자 정의 타입 가드 이용하기
- 개발자가 직접 타입 가드 함수를 정의할 수 있다.
- 사용자 정의 타입 가드를 통해 복잡한 타입이나 커스텀 타입에 대한 타입 가드를 구현할 수 있다.
예제 코드
interface Vehicle { make: string; } interface Car extends Vehicle { drive: () => void; } interface Truck extends Vehicle { haul: () => void; } type VehicleType = Car | Truck; function isCar(vehicle: VehicleType): vehicle is Car { return (vehicle as Car).drive !== undefined; } const myVehicle: VehicleType = { make: 'Toyota', drive: () => console.log('Driving'), }; if (isCar(myVehicle)) { myVehicle.drive(); } else { myVehicle.haul(); }
⇒ isCar
함수는 VehicleType
이 Car
인지 확인하는 사용자 정의 타입 가드이다.
⇒ 이를 통해 Car
와 Truck
을 안전하게 구분하여 각 타입에 맞는 메서드를 호출할 수 있다.
⑧ Discriminated Unions와 never 타입 이용하기
- Discriminated Unions는 유니온 타입의 각 멤버가 공통된 리터럴 속성을 가지는 패턴을 의미하며, 이를 통해 타입을 쉽게 구분할 수 있다.
- 또한,
never
타입을 이용하여 모든 경우를 처리했는지 컴파일 타임에 검증할 수 있다.
예제 코드
type IncrementAction = { type: 'increment'; amount: number; timestamp: number; user: string; }; type DecrementAction = { type: 'decrement'; amount: number; timestamp: number; user: string; }; type Action = IncrementAction | DecrementAction; function reducer(state: number, action: Action): number { switch (action.type) { case 'increment': return state + action.amount; case 'decrement': return state - action.amount; default: const exhaustiveCheck: never = action; throw new Error(`Unexpected action: ${exhaustiveCheck}`); } } const newState = reducer(15, { user: 'john', type: 'increment', amount: 5, timestamp: 123456, });
⇒ Action
타입은 IncrementAction
또는 DecrementAction
일 수 있다.
⇒ reducer
함수는 action.type
을 기준으로 분기하여 각 타입에 맞는 로직을 수행한다.
⇒ default
케이스에서 never
타입을 사용하여 모든 가능한 액션 타입이 처리되었는지 컴파일 시점에 확인한다.
타입 카드 활용 예제
예제 1 : 복합 데이터 타입 처리
type ApiResponse = SuccessResponse | ErrorResponse; interface SuccessResponse { status: 'success'; data: any; } interface ErrorResponse { status: 'error'; error: string; } function handleResponse(response: ApiResponse) { if (response.status === 'success') { console.log('Data:', response.data); } else { console.error('Error:', response.error); } }
⇒ ApiResponse
는 성공 응답과 오류 응답을 나타낼 수 있다.
⇒ status
속성을 기준으로 타입을 구분하고, 각 타입에 맞는 처리를 수행한다.
예제 2 : 클래스 인스턴스 구분
class Admin { name: string; privileges: string[]; constructor(name: string, privileges: string[]) { this.name = name; this.privileges = privileges; } } class Employee { name: string; startDate: Date; constructor(name: string, startDate: Date) { this.name = name; this.startDate = startDate; } } type Person = Admin | Employee; function printPersonInfo(person: Person) { console.log(`Name: ${person.name}`); if (person instanceof Admin) { console.log('Privileges:', person.privileges); } else { console.log('Start Date:', person.startDate); } } const admin = new Admin('Alice', ['manage-users']); const employee = new Employee('Bob', new Date()); printPersonInfo(admin); printPersonInfo(employee);
⇒ Person
타입은 Admin
또는 Employee
일 수 있다.
⇒ instanceof
를 사용하여 클래스 인스턴스를 구분하고, 각 클래스에 맞는 정보를 출력한다.
사용 시 주의사항
- 타입 가드는 컴파일 시점에 타입을 좁히는 것이 아니라, 런타임에서 실제 값의 타입을 확인한다.
- 따라서 타입 가드 조건이 정확해야 한다.
- 타입 가드를 사용한 후에도 변수의 타입이 일관되게 유지되도록 코드를 작성해야 한다.
- 예를 들어, 타입 가드 후에도 변수의 속성이 존재하지 않는 경우 오류가 발생할 수 있다.
- 모든 가능한 타입을 처리했는지 확인하기 위해
never
타입을 활용할 수 있다.- 특히 유니온 타입을 사용할 때 유용하다.
- 복잡한 객체나 다중 조건이 필요한 경우 사용자 정의 타입 가드를 정의하여 코드의 가독성과 재사용성을 높일 수 있다.
참고 사이트
Documentation - Creating Types from Types
An overview of the ways in which you can create more types from existing types.
www.typescriptlang.org
Documentation - Narrowing
Understand how TypeScript uses JavaScript knowledge to reduce the amount of type syntax in your projects.
www.typescriptlang.org
How to use type guards in TypeScript - LogRocket Blog
TypeScript uses built-in operators like typeof, instanceof, in, and is, which are used to determine if an object contains a property.
blog.logrocket.com
'Programming > TypeScript' 카테고리의 다른 글
[TypeScript] 클래스(Class) (0) | 2024.10.12 |
---|---|
[TypeScript] 제네릭(Generic) (0) | 2024.10.12 |
[TypeScript] 인터페이스(Interface) (0) | 2024.10.12 |
[TypeScript] Zod 라이브러리 (0) | 2024.10.11 |
[TypeScript] 모듈 방식 사용하기 (0) | 2024.10.10 |
[TypeScript] 인터페이스(Interface)와 타입 별칭(Type Alias) 비교 (0) | 2024.10.09 |
[TypeScript] ! 연산자(Non-null Assertion Operator) (0) | 2024.08.20 |
[TypeScript] MODULE_NOT_FOUND (Error: Cannot find module ~\react-scripts\bin\react-scripts.js) 오류 해결 방법 (0) | 2024.08.20 |