728x90
728x90

타입 가드(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'

 

inputDate 객체인지 확인하고, 맞다면 연도를 문자열로 반환한다.

⇒ 그렇지 않으면 원래 문자열을 반환한다.

 

⑥ 타입 프리디케이트(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 블록에서는 personStudent임을 타입스크립트가 인식하여 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 함수는 VehicleTypeCar인지 확인하는 사용자 정의 타입 가드이다.
⇒ 이를 통해 CarTruck을 안전하게 구분하여 각 타입에 맞는 메서드를 호출할 수 있다.

 

⑧ 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

 

728x90
728x90

타입 가드(Type Guard)들어가며타입 가드(Type Guard)개념방법① typeof  연산자 이용하기② 동등성 좁히기(Equality Narrowing)③ in 연산자 이용하기④ Truthy/Falsy 값 이용하기⑤ instanceof  연산자 이용하기⑥ 타입 프리디케이트(Type Predicate) 이용하기⑦ 사용자 정의 타입 가드 이용하기⑧ Discriminated Unions와 never 타입 이용하기타입  카드 활용 예제사용 시 주의사항참고 사이트