WeniVooks

검색

타입스크립트 베이스캠프

타입 가드

1. 타입 가드

타입 가드를 사용하면 특정 스코프 내에서 타입을 보장할 수 있습니다. "이 객체가 특정 타입인지 확인하고, 맞다면 TypeScript에게 그 사실을 알려주는" 방법이라고 생각하시면 됩니다.

1.1 typeof 타입 가드

typeof는 JavaScript의 typeof처럼 Array를 Object로 반환합니다. 따라서 객체를 취급할 때에는 주의해야 합니다.

function processValue(value: string | number) {
    if (typeof value === "string") {
        return value.toUpperCase(); // string으로 타입 보장
    }
    return value.toFixed(2); // number로 타입 보장
}
function processValue(value: string | number) {
    if (typeof value === "string") {
        return value.toUpperCase(); // string으로 타입 보장
    }
    return value.toFixed(2); // number로 타입 보장
}

typeof는 javascript와 typescript모두 연산이 정확하지 않기 때문에 아래와 같이 별도의 사용자 정의 함수를 만들어 사용하는 것을 권합니다. javascript에서 사용 가능한 코드입니다.

function typeCheck(value) {
    const return_value = Object.prototype.toString.call(value);
    const type = return_value.substring(
        return_value.indexOf(" ") + 1,
        return_value.indexOf("]")
    );
    return type.toLowerCase();
}
function typeCheck(value) {
    const return_value = Object.prototype.toString.call(value);
    const type = return_value.substring(
        return_value.indexOf(" ") + 1,
        return_value.indexOf("]")
    );
    return type.toLowerCase();
}

타입스크립트에서는 아래와 같이 사용할 수 있습니다.

function typeCheck(value: any): string {
    const return_value = Object.prototype.toString.call(value);
    const type = return_value.substring(
        return_value.indexOf(" ") + 1,
        return_value.indexOf("]")
    );
    return type.toLowerCase();
}
 
console.log(typeCheck([]));
console.log(typeCheck(null));
function typeCheck(value: any): string {
    const return_value = Object.prototype.toString.call(value);
    const type = return_value.substring(
        return_value.indexOf(" ") + 1,
        return_value.indexOf("]")
    );
    return type.toLowerCase();
}
 
console.log(typeCheck([]));
console.log(typeCheck(null));
1.2 instanceof 타입 가드
class Dog {
    bark() { return "Woof!"; }
}
 
class Cat {
    meow() { return "Meow!"; }
}
 
function makeSound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        return animal.bark();
    }
    return animal.meow();
}
 
const dog = new Dog();
const cat = new Cat();
 
console.log(makeSound(dog)); // Woof!
console.log(makeSound(cat)); // Meow!
class Dog {
    bark() { return "Woof!"; }
}
 
class Cat {
    meow() { return "Meow!"; }
}
 
function makeSound(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        return animal.bark();
    }
    return animal.meow();
}
 
const dog = new Dog();
const cat = new Cat();
 
console.log(makeSound(dog)); // Woof!
console.log(makeSound(cat)); // Meow!
1.3 사용자 정의 타입 가드
interface Fish {
    swim(): void;
}
 
interface Bird {
    fly(): void;
}
 
function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}
 
function move(pet: Fish | Bird) {
    if (isFish(pet)) {
        pet.swim();
    } else {
        pet.fly();
    }
}
 
const fish = {
    swim: () => console.log("물고기가 헤엄칩니다.")
};
 
const bird = {
    fly: () => console.log("새가 날아갑니다.")
};
 
move(fish); // "물고기가 헤엄칩니다 🐠"
move(bird); // "새가 날아갑니다 🐦"
interface Fish {
    swim(): void;
}
 
interface Bird {
    fly(): void;
}
 
function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}
 
function move(pet: Fish | Bird) {
    if (isFish(pet)) {
        pet.swim();
    } else {
        pet.fly();
    }
}
 
const fish = {
    swim: () => console.log("물고기가 헤엄칩니다.")
};
 
const bird = {
    fly: () => console.log("새가 날아갑니다.")
};
 
move(fish); // "물고기가 헤엄칩니다 🐠"
move(bird); // "새가 날아갑니다 🐦"

pet is Fish는 TypeScript의 타입 술어(Type Predicate)로, 함수가 true를 반환할 때 해당 매개변수가 Fish 타입이라는 것을 TypeScript에게 알려주는 역할을 합니다. 쉽게 말해서, 이 조건이 참이면 pet은 Fish 타입이다라고 TypeScript의 타입 시스템에게 알려주는 것입니다.

(pet as Fish).swim !== undefined는 타입 단언(Type Assertion)을 사용한 코드입니다. pet 객체를 Fish 타입이라고 가정(타입 단언)하고, swim이라는 메서드가 존재하는지 확인합니다. 만약 swim 메서드가 존재한다면 true를 반환하여 이 객체가 Fish 타입이라고 판단합니다. 즉, "물고기라면 반드시 가지고 있어야 할 swim 메서드가 있는지 확인"하는 것입니다.

2. 연습문제

  1. 다음과 같이 문자열 또는 숫자를 받아서 해당 값의 길이를 반환하는 함수를 작성하세요.
  • 문자열이 들어오면 문자열의 길이를 반환
  • 숫자가 들어오면 숫자를 문자열로 변환한 후 그 길이를 반환
  1. 다음과 같이 두 가지 타입의 동물 객체가 있습니다. 각 동물의 특성에 맞게 소리를 출력하는 함수를 작성하세요.
class Dog {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    bark() {
        return "멍멍!";
    }
}
 
class Cat {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    meow() {
        return "야옹!";
    }
}
 
// 여기에 makeSound 함수를 작성하세요.
// Dog나 Cat을 받아서 각각 알맞은 소리를 반환해야 합니다.
class Dog {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    bark() {
        return "멍멍!";
    }
}
 
class Cat {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    meow() {
        return "야옹!";
    }
}
 
// 여기에 makeSound 함수를 작성하세요.
// Dog나 Cat을 받아서 각각 알맞은 소리를 반환해야 합니다.
  1. 다음 코드에 타입 가드를 추가해보세요.
interface Square {
    kind: "square";
    size: number;
}
 
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type Shape = Square | Rectangle | Circle;
 
// 도형의 면적을 계산하는 함수를 작성하세요.
function calculateArea(shape: Shape): number {
    // 여기에 구현
}
interface Square {
    kind: "square";
    size: number;
}
 
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type Shape = Square | Rectangle | Circle;
 
// 도형의 면적을 계산하는 함수를 작성하세요.
function calculateArea(shape: Shape): number {
    // 여기에 구현
}

3. 연습문제 정답

  1. 문자열 또는 숫자를 받아서 해당 값의 길이를 반환하는 함수
function getLength(value: string | number): number {
    if (typeof value === "string") {
        return value.length;
    }
    return String(value).length;
}
 
// 사용 예시
console.log(getLength("hello"));  // 5
console.log(getLength(12345));    // 5
function getLength(value: string | number): number {
    if (typeof value === "string") {
        return value.length;
    }
    return String(value).length;
}
 
// 사용 예시
console.log(getLength("hello"));  // 5
console.log(getLength(12345));    // 5
  1. 각 동물의 특성에 맞게 소리를 출력하는 함수
class Dog {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    bark() {
        return "멍멍!";
    }
}
 
class Cat {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    meow() {
        return "야옹!";
    }
}
 
function makeSound(animal: Dog | Cat): string {
    if (animal instanceof Dog) {
        return `${animal.name}이(가) ${animal.bark()}`;
    }
    return `${animal.name}이(가) ${animal.meow()}`;
}
 
// 사용 예시
const dog = new Dog("멍멍이");
const cat = new Cat("야옹이");
 
console.log(makeSound(dog));  // "멍멍이가 멍멍!"
console.log(makeSound(cat));  // "야옹이가 야옹!"
class Dog {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    bark() {
        return "멍멍!";
    }
}
 
class Cat {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    meow() {
        return "야옹!";
    }
}
 
function makeSound(animal: Dog | Cat): string {
    if (animal instanceof Dog) {
        return `${animal.name}이(가) ${animal.bark()}`;
    }
    return `${animal.name}이(가) ${animal.meow()}`;
}
 
// 사용 예시
const dog = new Dog("멍멍이");
const cat = new Cat("야옹이");
 
console.log(makeSound(dog));  // "멍멍이가 멍멍!"
console.log(makeSound(cat));  // "야옹이가 야옹!"
  1. 도형의 면적을 계산하는 함수 (타입 가드 사용)
interface Square {
    kind: "square";
    size: number;
}
 
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type Shape = Square | Rectangle | Circle;
 
// 타입 가드 함수들
function isSquare(shape: Shape): shape is Square {
    return shape.kind === "square";
}
 
function isRectangle(shape: Shape): shape is Rectangle {
    return shape.kind === "rectangle";
}
 
function isCircle(shape: Shape): shape is Circle {
    return shape.kind === "circle";
}
 
function calculateArea(shape: Shape): number {
    if (isSquare(shape)) {
        return shape.size * shape.size;
    }
    
    if (isRectangle(shape)) {
        return shape.width * shape.height;
    }
    
    if (isCircle(shape)) {
        return Math.PI * shape.radius * shape.radius;
    }
    
    // 모든 케이스를 처리했지만, TypeScript의 타입 체크를 위해 필요
    const unreachable: never = shape;
    throw new Error(`Unhandled shape type: ${unreachable}`);
}
 
// 사용 예시
const square: Square = { kind: "square", size: 5 };
const rectangle: Rectangle = { kind: "rectangle", width: 4, height: 6 };
const circle: Circle = { kind: "circle", radius: 3 };
 
console.log(calculateArea(square));    // 25
console.log(calculateArea(rectangle)); // 24
console.log(calculateArea(circle));    // 약 28.27
interface Square {
    kind: "square";
    size: number;
}
 
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
 
interface Circle {
    kind: "circle";
    radius: number;
}
 
type Shape = Square | Rectangle | Circle;
 
// 타입 가드 함수들
function isSquare(shape: Shape): shape is Square {
    return shape.kind === "square";
}
 
function isRectangle(shape: Shape): shape is Rectangle {
    return shape.kind === "rectangle";
}
 
function isCircle(shape: Shape): shape is Circle {
    return shape.kind === "circle";
}
 
function calculateArea(shape: Shape): number {
    if (isSquare(shape)) {
        return shape.size * shape.size;
    }
    
    if (isRectangle(shape)) {
        return shape.width * shape.height;
    }
    
    if (isCircle(shape)) {
        return Math.PI * shape.radius * shape.radius;
    }
    
    // 모든 케이스를 처리했지만, TypeScript의 타입 체크를 위해 필요
    const unreachable: never = shape;
    throw new Error(`Unhandled shape type: ${unreachable}`);
}
 
// 사용 예시
const square: Square = { kind: "square", size: 5 };
const rectangle: Rectangle = { kind: "rectangle", width: 4, height: 6 };
const circle: Circle = { kind: "circle", radius: 3 };
 
console.log(calculateArea(square));    // 25
console.log(calculateArea(rectangle)); // 24
console.log(calculateArea(circle));    // 약 28.27
3.2 고급 타입3.4 타입 관련 고급 기능