-
[TypeScript] 고급타입 - 2TypeScript 2022. 9. 22. 00:26728x90
타입 별칭(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