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'

 

⇒ @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

 

728x90
728x90