Typescript - Type Guards 타입 가드

Posted by Juri on May 26, 2022

참고 : typescript 핸드북

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Bird {
    fly();
    layEggs();
}
interface Fish {
    swim();
    layEggs();
}
function getSmallPet(): Fish | Bird {
    // ...
}

let pet = getSmallPet();

유니온 타입은 값의 타입이 겹쳐질 수 있는 상황에 유용하지만 아래와 같이 각 프로퍼티들에 접근하는 것은 불가능하다. 유니언 타입의 모든 구성 성분을 가지고 있다고 보장되는 멤버에만 접근할 수 있다. 아래와 같이 각 인터페이스에 있는 swimfly에 접근할 수 없는 반면 공통으로 갖고 있는 프로퍼티인 layEggs에는 접근이 가능하다.

1
2
3
4
5
6
7
8
pet.layEggs(); // 성공
pet.swim(); // 오류

if (pet.swim) {
    pet.swim();
} else if (pet.fly) {
    pet.fly();
}

코드를 동작하게 하기 위해서 타입 단언을 사용할 수 있다.

1
2
3
4
5
if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}

User-Defined 타입가드

타입 가드는 스코프 안에서의 타입을 보장하는 런타임 검사를 수행한다는 표현식이다. {parameter} is (Type) 형식으로 어떠한 인자명은 어떠한 타입이다라고 명시해주는 역할을 한다.

type predicates 사용하기

타입 가드를 정의하기 위해 반환 타입이 type predicate인 함수를 정의하면 된다. 아래 함수는 인자로 Fish 나 Bird 타입 두가지를 가질 수 있는 값을 전달하면 함수에서 Fish 타입일 경우에만 true로 전달한다.

1
2
3
function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

isFish가 변수와 함께 호출될 때마다, typescript는 기존 타입과 호환된다면 그 변수를 특정 타입으로 제한할 것이다.

1
2
3
4
5
if (isFish(pet)) {
    pet.swim();
} else {
    pet.fly();
}

in operator 사용하기

n in x 에서 n은 문자열 리터럴 혹은 문자열 리터럴 타입이고 x는 유니언 타입이다. true 분기에서 프로퍼티 n을 갖는 타입으로 타입을 좁힐 수 있다.

1
2
3
4
5
6
function move(pet: Fish | Bird) {
    if ("swim" in pet) {
        return pet.swim();
    }
    return pet.fly();
}

type of 타입가드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function isNumber(x: any): x is number {
    return typeof x === "number";
}

function isString(x: any): x is string {
    return typeof x === "string";
}

function padLeft(value: string, padding: string | number) {
    if (isNumber(padding)) {
        return Array(padding + 1).join(" ") + value;
    }
    if (isString(padding)) {
        return padding + values;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

typescript는 typeof를 type guard로 인식해 typeof x === “number”로 함수를 추상할 필요가 없다.

1
2
3
4
5
6
7
8
9
function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

typeof와 함께 사용할 수 있는 타입으로 number, string, boolean, symbol이 있다.

instanceof 타입가드

instanceof 타입가드는 생성자 함수를 사용해 타입을 좁히는 방법이다. instanceof의 오른쪽은 생성자 함수여야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
interface Padder {
    getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) {}
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}

class StringPadder implements Padder {
    constructor(private value: string) {}
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5
        ? new SpaceRepeatingPadder(4)
        : new StringPadder("  ");
}

let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder;
}
if (padder instanceof StringPadder) {
    padder;
}

padder의 타입은 SpaceRepeatingPadder | StringPadder 이며 instanceof의 오른쪽에 있는 생성자 함수를 타입으로 좁혀진다.