ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TypeScript] 고급타입-1
    TypeScript 2022. 9. 21. 23:26
    728x90

    교차 타입(Intersection Types)

    교차 타입은 여러 타입을 하나로 결합합니다.

    Person & Serializable & LoggablePerson, Serializable, Loggable 타입의 모든 멤버를 갖습니다.

    아래는 믹스인을 만드는 간단한 예제입니다.

    function extend<First, Second>(first: First, second: Second): First & Second {
        const result: Partial<First & Second> = {};
        for (const prop in first) {
            if (first.hasOwnProperty(prop)) {
                (result as First)[prop] = first[prop];
            }
        }
        for (const prop in second) {
            if (second.hasOwnProperty(prop)) {
                (result as Second)[prop] = second[prop];
            }
        }
        return result as First & Second;
    }
    
    class Person {
        constructor(public name: string) { }
    }
    
    interface Loggable {
        log(name: string): void;
    }
    
    class ConsoleLogger implements Loggable {
        log(name) {
            console.log(`Hello, I'm ${name}.`);
        }
    }
    
    const jim = extend(new Person('Jim'), ConsoleLogger.prototype);
    jim.log(jim.name);

    유니언 타입(Union Types)

    유니언 타입은 교차 타입과 밀접하게 관련되어 있지만, 매우 다르게 사용됩니다.

    /**
     * 문자열을 받고 왼쪽에 "padding"을 추가합니다.
     * 만약 'padding'이 문자열이라면, 'padding'은 왼쪽에 더해질 것입니다.
     * 만약 'padding'이 숫자라면, 그 숫자만큼의 공백이 왼쪽에 더해질 것입니다.
     */
    function padLeft(value: string, padding: any) {
        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}'.`);
    }
    
    padLeft("Hello world", 4); // "    Hello world"를 반환합니다.
    let indentedString = padLeft("Hello world", true); // 컴파일 타임에 통과되고, 런타임에 오류.

    만약 paddingany 타입이 아닌 numberstring으로 이루어진 유니언 타입이라면 컴파일중에 오류가 발생하게 됩니다.

    function padLeft(value: string, padding: string | number) {
        // ...
    }
    
    let indentedString = padLeft("Hello world", true); // 컴파일 중에 오류

    유니언 타입의 핵심은 유니언에 있는 모든 타입에 공통 멤버에만 접근이 가능하다는 것 입니다.

    interface Bird {
        fly();
        layEggs();
    }
    
    interface Fish {
        swim();
        layEggs();
    }
    
    function getSmallPet(): Fish | Bird {
        // ...
    }
    
    let pet = getSmallPet();
    pet.layEggs(); // 성공
    pet.swim();    // 오류

    getSmallPetFish 또는 Bird 멤버일 수 있는데 swim 메서드는 Bird 멤버에선 사용 불가, fly 메서드는 Fish 멤버에서 사용이 불가합니다. 따라서 TypeScript는 확실히 사용 가능한 layEgg메서드만 호출할 수 있다고 이해하면 될 것 같습니다.

    타입 가드와 차별 타입(Type Guards and Differentiating Types)

    유니언 타입은 값의 타입이 겹쳐질 수 있는 상황을 모델링하는데 유용합니다.

    양쪽 멤버에 공통으로 존재하지 않는 메서드에 접근하기 위해서는 타입 단언 as 를 사용해야 합니다.

    let pet = getSmallPet();
    
    if ((pet as Fish).swim) {
        (pet as Fish).swim();
    } else if ((pet as Bird).fly) {
        (pet as Bird).fly();
    }

    사용자-정의 타입 가드(User-Defined Type Guards)

    위와같이 사용하면 if문마다 타입 단언을 사용해야합니다. TypeScript는 타입 가드를 통해서 스코프 안에서의 타입을 보장하는 런타임 검사를 수행하는 방법이 존재합니다.

    타입 서술어 사용하기(Using type predicates)

    타입 가드를 정의하기 위해서는 반환 타입이 타입 서술어인 함수를 정의해야 합니다.

    function isFish(pet: Fish | Bird): pet is Fish {
        return (pet as Fish).swim !== undefined;
    }
    // 이제 'swim'과 'fly'에 대한 모든 호출은 허용됩니다
    
    if (isFish(pet)) {
        pet.swim();
    }
    else {
        pet.fly();
    }

    pet is Fish는 타입 서술어입니다.

    • 타입 서술어 : parameterName is Type 형태이고, parameterName은 반드시 함수 시그니처의 매개변수 이름이어야 합니다.

    isFish가 호출되면 TypeScript는 그 변수를 특정 타입으로 제한합니다.

    따라서 if문에선 반드시 Fish임을 뜻하고, else문에선 반드시 Fish가 아니므로 Bird를 뜻합니다.

    in 연산자 사용하기 (Using the in operator)

    function move(pet: Fish | Bird) {
        if ("swim" in pet) {
            return pet.swim();
        }
        return pet.fly();
    }

    in 연산자는 타입을 좁히는 용도로 사용됩니다.

    in 연산자의 문자열 리터럴이거나 문자열 리터럴 타입이 올 수 있습니다.

    in 연산자의 에는 유니언 타입이 올 수 있습니다.

    “swim” in pet에서 petswim을 가지는 타입으로 좁혀지므로 Fish가 되고, 그게 아니면 Bird가 되기때문에 마지막에 return pet.fly()가 가능해집니다.

    typeof 타입 가드 (typeof type guards)

    타입 서술어를 이용하면 아래와 같습니다.

    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 + value;
        }
        throw new Error(`Expected string or number, got '${padding}'.`);
    }

    하지만 매번 저런 함수를 정의하는 것은 너무 귀찮은데요,

    TypeScript는 typeof x === ‘number’를 함수로 추상하지 않아도 됩니다.

    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 v === ‘typename’에서 typename는 반드시 number, string, boolean 그리고 symbol이어야 합니다. 그 외의 타입은 타입 가드 표현식으로 인식되지 않습니다.

    instanceof 타입 가드 (instanceof type guards)

    instanceof 타입 가드는 생성자 함수를 사용하여 타입을 좁히는 방식입니다.

    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("  ");
    }
    
    // 타입은 'SpaceRepeatingPadder | StringPadder' 입니다
    let padder: Padder = getRandomPadder();
    
    if (padder instanceof SpaceRepeatingPadder) {
        padder; // 타입은 'SpaceRepeatingPadder'으로 좁혀집니다
    }
    if (padder instanceof StringPadder) {
        padder; // 타입은 'StringPadder'으로 좁혀집니다
    }

    instanceof의 오른쪽은 반드시 생성자 함수여야 합니다.

    TypeScript는 타입을 다음과 같이 좁힙니다

    1. 함수의 prototype 프로퍼티 타입이 any가 아닌 경우
    2. 타입의 생성자 시그니처에서 반환된 유니언 타입일 경우

    널러블 타입(Nullable types)

    TypeScript는 nullundefined를 다르게 처리합니다.

    nullundefined는 어떤 타입에든 할당할 수 있다고 간주됩니다. 따라서 방지하고 싶어도 어떤 타입에 할당되는 것을 방지 할 수 없습니다.

    따라서 이건 —strictNullChecks 플래그로 해결을 합니다.

    —strictNullChecks : true 면 변수 선언 시 null, undefined를 포함하지 않습니다.

    let s = "foo";
    s = null; // 오류, 'null'은 'string'에 할당할 수 없습니다
    let sn: string | null = "bar";
    sn = null; // 성공
    
    sn = undefined; // 오류, 'undefined'는 'string | null'에 할당할 수 없습니다.
    // null과 undefined를 다르게 간주하기 때문에

    선택적 매개변수와 프로퍼티(Optional parameters and properties)

    --strictNullChecks를 적용하면, 선택적 매개변수 ?| undefined를 자동으로 추가합니다

    function f(x: number, y?: number) {
        return x + (y || 0);
    }
    f(1, 2);
    f(1);
    f(1, undefined);
    f(1, null); // 오류, 'null'은 'number | undefined'에 할당할 수 없습니다

    타입 가드와 타입 단언(Type guards and type assertions)

    null 타입이 유니언으로 구현되기 대문에, null을 제거하려면 타입 가드를 사용해야 합니다.

    function f(sn: string | null): string {
        if (sn == null) {
            return "default";
        }
        else {
            return sn;
        }
    }

    조금더 간단하게 아래와 같이도 사용 가능합니다.

    function f(sn: string | null): string {
        return sn || "default";
    }

    타입 단언 연산자 !를 후위에 표기하여 null과 undefined 타입을 제거할 수 있습니다.

    function broken(name: string | null): string {
      function postfix(epithet: string) {
        return name.charAt(0) + '.  the ' + epithet; // 오류, 'name'은 아마도 null 입니다
      }
      name = name || "Bob";
      return postfix("great");
    }
    
    function fixed(name: string | null): string {
      function postfix(epithet: string) {
        return name!.charAt(0) + '.  the ' + epithet; // 성공
      }
      name = name || "Bob";
      return postfix("great");
    }
    728x90

    'TypeScript' 카테고리의 다른 글

    [TypeScript] 선언병합  (1) 2022.09.23
    [TypeScript] 고급타입 - 2  (1) 2022.09.22
    [TypeScript] 제네릭  (0) 2022.09.17
    [TypeScript] 열거형  (1) 2022.09.16
    [TypeScript] 클래스  (0) 2022.09.15

    댓글

Designed by Tistory.