ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [TypeScript] 고급타입 - 2
    TypeScript 2022. 9. 22. 00:26
    728x90

    타입 별칭(Type Aliases)

    타입 별칭은 새로운 타입을 만드는게 아닌 그 타입을 나타내는 새로운 이름을 만드는 것 입니다.

    type Name = string;
    type NameResolver = () => string;
    type NameOrResolver = Name | NameResolver;
    function getName(n: NameOrResolver): Name {
        if (typeof n === "string") {
            return n;
        }
        else {
            return n();
        }
    }
    • 프로퍼티 내에 자기 자신을 참조할 수 있습니다.
    type Tree<T> = {
        value: T;
        left: Tree<T>;
        right: Tree<T>;
    }
    • 타입별칭은 제네릭이 될 수 있습니다.
    type Container<T> = { value: T };
    • 교차 타입을 사용할 수 있습니다.
    type LinkedList<T> = T & { next: LinkedList<T> };
    
    interface Person {
        name: string;
    }
    
    var people: LinkedList<Person>;
    var s = people.name;
    var s = people.next.name;
    var s = people.next.next.name;
    var s = people.next.next.next.name;
    • 선언과 동시에 사용하는 것은 불가능합니다.
    type Yikes = Array<Yikes>; // 오류

    인터페이스 vs 타입 별칭 (Interface vs Type Aliases)

    거의 동일하다고 볼 수 있습니다.

    확장의 방식이 다르고, 확장했을 때 충돌을 잡는 방식이 다를뿐입니다.

    문자열 리터럴 타입(String Literal Types)

    문자열 값은 유니언 타입, 타입 가드, 타입 별칭과 잘 결합할 수 있습니다.

    아래의 예제 Easing 타입은 반드시 3개의 문자열만 올 수 있습니다.

    type Easing = "ease-in" | "ease-out" | "ease-in-out";
    class UIElement {
        animate(dx: number, dy: number, easing: Easing) {
            if (easing === "ease-in") {
                // ...
            }
            else if (easing === "ease-out") {
            }
            else if (easing === "ease-in-out") {
            }
            else {
                // 오류! null이나 undefined를 전달하면 안됩니다
            }
        }
    }
    
    let button = new UIElement();
    button.animate(0, 0, "ease-in");
    button.animate(0, 0, "uneasy"); // 오류: "uneasy"는 여기서 허용하지 않습니다

    숫자 리터럴 타입(numeric Literal Types)

    숫자 리터럴도 타입으로 지정할 수 있습니다. 명시적으로 숫자 리터럴을 작성하는 경우는 거의 없지만, 이슈를 좁혀 버그를 잡을 때 많이 사용합니다.

    function foo(x: number) {
        if (x !== 1 || x !== 2) {
            //         ~~~~~~~
            // '!==' 연산자는 '1'과 '2' 타입에 적용할 수 없습니다.
        }
    }

    열거형 멤버 타입(Enum Memeger Types)

    열거형 멤버는 모든 멤버가 리터럴로 초기화될 때 타입을 가집니다.

    판별 유니언(Discriminated Unions)

    먼저 kind 프로퍼티를 공통으로 갖고있는 인터페이스를 3개 선언합니다.

    interface Square {
        kind: "square";
        size: number;
    }
    interface Rectangle {
        kind: "rectangle";
        width: number;
        height: number;
    }
    interface Circle {
        kind: "circle";
        radius: number;
    }

    여기서 kind 프로퍼티는 판별식 혹은 태그 **라고 부릅니다. 이제 유니언으로 집어넣어 사용해보겠습니다.

    type Shape = Square | Rectangle | Circle;
    function area(s: Shape) {
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
        }
    }

    엄격한 검사(Exhaustiveness checking)

    triangle 케이스가 처리되지 않아 에러가 발생하게 됩니다.

    type Shape = Square | Rectangle | Circle | Triangle;
    function area(s: Shape) {
        switch (s.kind) {
            case "square": return s.size * s.size;
            case "rectangle": return s.height * s.width;
            case "circle": return Math.PI * s.radius ** 2;
        }
        // 여기서 오류 발생 - "triangle"의 케이스를 처리하지 않음
    }

    다형성 this 타입(Polymorphic this types)

    다형성 this 타입은 포함하는 클래스나 인터페이스의 하위 타입을 나타냅니다.

    class BasicCalculator {
        public constructor(protected value: number = 0) { }
        public currentValue(): number {
            return this.value;
        }
        public add(operand: number): this {
            this.value += operand;
            return this;
        }
        public multiply(operand: number): this {
            this.value *= operand;
            return this;
        }
        // ... 다른 연산들은 여기에 작성 ...
    }
    
    let v = new BasicCalculator(2)
                .multiply(5)
                .add(1)
                .currentValue();//11

    위 클래스의 add와 multiply는 this 타입을 사용하기 때문에 이를 extend하여 새로운 클래스가 메서드를 이용할 수 있습니다.

    class ScientificCalculator extends BasicCalculator {
        public constructor(value = 0) {
            super(value);
        }
        public sin() {
            this.value = Math.sin(this.value);
            return this;
        }
        // ... 다른 연산들은 여기에 작성 ...
    }
    
    let v = new ScientificCalculator(2)
            .multiply(5)
            .sin()
            .add(1)
            .currentValue();

    인덱스 타입(Index types)

    function pluck<T, K extends keyof T>(o: T, propertyNames: K[]): T[K][] {
      return propertyNames.map(n => o[n]);
    }
    
    interface Car {
        manufacturer: string;
        model: string;
        year: number;
    }
    let taxi: Car = {
        manufacturer: 'Toyota',
        model: 'Camry',
        year: 2014
    };
    
    // Manufacturer과 model은 둘 다 문자열 타입입니다,
    // 그래서 둘 다 타이핑된 문자열 배열로 끌어낼 수 있습니다.
    let makeAndModel: string[] = pluck(taxi, ['manufacturer', 'model']);
    
    // 만약 model과 year를 끌어내려고 하면,
    // 유니언 타입의 배열: (string | number)[] 을 얻게됩니다.
    let modelYear = pluck(taxi, ['model', 'year'])

    컴파이러는 Car의 프로퍼티로 manufacturer과 model이 있는지 확인합니다.

    let carProps: keyof Car; // ('manufacturer' | 'model' | 'year')의 유니언

    위 처럼 keyof 를 사용하면 'manufacturer' | 'model' | 'year’ 와 완전히 호환됩니다.

    차이로는 Car에 새로운 프로퍼티가 추가된다면 새로운 프로퍼티또한 carProps에 추가된다는 점이 다릅니다.

    인덱스 타입과 인덱스 시그니처(Index types and index signatures)

    인덱스 시그니처 매개변수 타입은 반드시 string 혹은 number여야 합니다.

    interface Dictionary<T> {
        [key: string]: T;
    }
    let keys: keyof Dictionary<number>; // string | number
    let value: Dictionary<number>['foo']; // number
    let value: Dictionary<number>[3]; // number
    interface Dictionary<T> {
        [key: number]: T;
    }
    let keys: keyof Dictionary<number>; // 숫자
    let value: Dictionary<number>['foo']; // 오류, 프로퍼티 'foo'는 타입 'Dictionary<number>'에 존재하지 않습니다.
    let value: Dictionary<number>[42]; // 숫자

    매핑 타입(Mapped types)

    type Keys = 'option1' | 'option2';
    type Flags = { [K in Keys]: boolean };

    위 예제의 Flag는 아래와 같습니다.

    type Flags = {
        option1: boolean;
        option2: boolean;
    }

    매핑 타입의 추론(Inference from mapped types)

    위에는 래핑하는 과정이었다면 이제는 언래핑하는 방법입니다.

    function unproxify<T>(t: Proxify<T>): T {
        let result = {} as T;
        for (const k in t) {
            result[k] = t[k].get();
        }
        return result;
    }
    
    let originalProps = unproxify(proxyProps);

    조건부 타입(Conditional Types)

    조건부 타입은 조건에 따라 타입을 할당하는 것 입니다.

    type TypeName<T> =
        T extends string ? "string" :
        T extends number ? "number" :
        T extends boolean ? "boolean" :
        T extends undefined ? "undefined" :
        T extends Function ? "function" :
        "object";
    
    type T0 = TypeName<string>;  // "string"
    type T1 = TypeName<"a">;  // "string"
    type T2 = TypeName<true>;  // "boolean"
    type T3 = TypeName<() => void>;  // "function"
    type T4 = TypeName<string[]>;  // "object"
    interface Foo {
        propA: boolean;
        propB: boolean;
    }
    
    declare function f<T>(x: T): T extends Foo ? string : number;
    
    function foo<U>(x: U) {
        // 'U extends Foo ? string : number' 타입을 가지고 있습니다
        let a = f(x);
    
        // 이 할당은 허용됩니다!
        let b: string | number = a;
    }

    위 예제에서 변수 a 는 아직 분기를 결정하지 못한 조건부 타입입니다. 이것은 string일수도 number일수도 있다는 것 이므로 string | number 타입으로 할당할 수 있습니다.

    분산 조건부 타입(Distributive conditional types)

    T에 대한 타입 인수 A | B를 사용하여 T extends U ? X : Y를 인스턴스화하면 (A extends U ? X : Y) | (B extends U ? X : Y)로 결정됩니다.

    type BoxedValue<T> = { value: T };
    type BoxedArray<T> = { array: T[] };
    type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>;
    
    type T20 = Boxed<string>;  // BoxedValue<string>;
    type T21 = Boxed<number[]>;  // BoxedArray<number>;
    type T22 = Boxed<string | number[]>;  // BoxedValue<string> | BoxedArray<number>;

    조건부 타입의 분선 프로퍼티는 유니언 타입을 필터링 할 때 편하게 사용할 수 있습니다.

    type Diff<T, U> = T extends U ? never : T;  // U에 할당할 수 있는 타입을 T에서 제거
    type Filter<T, U> = T extends U ? T : never;  // U에 할당할 수 없는 타입을 T에서 제거
    
    type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "b" | "d"
    type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">;  // "a" | "c"
    type T32 = Diff<string | number | (() => void), Function>;  // string | number
    type T33 = Filter<string | number | (() => void), Function>;  // () => void
    
    type NonNullable<T> = Diff<T, null | undefined>;  // T에서 null과 undefined를 제거
    
    type T34 = NonNullable<string | number | undefined>;  // string | number
    type T35 = NonNullable<string | string[] | null | undefined>;  // string | string[]
    
    function f1<T>(x: T, y: NonNullable<T>) {
        x = y;  // 성공
        y = x;  // 오류
    }
    
    function f2<T extends string | undefined>(x: T, y: NonNullable<T>) {
        x = y;  // 성공
        y = x;  // 오류
        let s1: string = x;  // 오류
        let s2: string = y;  // 성공
    }

    참고

    그 외 : https://typescript-kr.github.io/pages/advanced-types.html

    728x90

    'TypeScript' 카테고리의 다른 글

    이펙티브 타입스크립트 아이템 1~3  (1) 2023.06.02
    [TypeScript] 선언병합  (1) 2022.09.23
    [TypeScript] 고급타입-1  (0) 2022.09.21
    [TypeScript] 제네릭  (0) 2022.09.17
    [TypeScript] 열거형  (1) 2022.09.16

    댓글

Designed by Tistory.