https://www.youtube.com/watch?v=ViS8DLd6o-E
위 영상을 보고 공부한 것을 기록하는 포스트입니다.
제너릭에 따라 조건부 타입 설정하기
interface StringContainer {
value: string;
}
interface NumberContainer {
value: number;
}
type Item1<T> = {
id: T;
container: T extends string ? StringContainer : NumberContainer;
}
제너릭은 함수나 변수를 호출시 타입이 결정되도록 하는 기능
위 코드에서 T extends string은 제너릭 T가 string 타입에 할당 가능한가? 즉 서브타입이냐에 따라
타입이 결정되도록 삼항연산자를 사용하였다.
하지만 만약에 위 코드에서 제너릭 타입이 number, string이 둘다 아니라면 ???
type Item2<T> = {
id: T extends string | number ? T : never;
container: T extends string
? StringContainer
: T extends number
? NumberContainer
: never;
}
const item3 : Item2<boolean> = {
id:true, // Type 'boolean' is not assignable to type 'never'.
container: null // Type 'never' is not assignable to type 'never'.
}
never 라는 키워드를 사용한다.
never는 사용불가하다.
이중 삼항연산자를 사용하여 string과 number 타입이 아니라면 never를 사용한다.
ArrayFilter<T>
arrayfilter는 말그대로 배열 타입을 걸러내는 커스텀 타입
type ArrayFilter<T> = T extends any[] ? T : never;
type StringsOrNumbers = ArrayFilter<string | number | string[] | number[]>;
위 코드에서 StringsOrNumbers 타입은
type StringsOrNumbers = string[] | number[];
위와 같이 추론된다. any[]는 제너릭 T가 배열에 할당가능한지 판단하고 T를 반환한다.
제너릭 타입 함수 파라미터에 타입 제약걸기 ★★
함수 파라미터에 제너릭타입을 받을 때 특정 타입으로만 추론될 수 있게 제약을 걸고 싶다면
interface Table {
id: string;
chairs: string[];
}
interface Dino {
id: number;
legs: number;
}
interface World{
getItem<T extends string | number>(id: T): T extends string ? Table : Dino;
}
let world:World = null as any; // 명시적으로 any 할당
const dino = world.getItem(10);
const what = world.getItem(true); // Error! Argument of type 'boolean' is not assignable to parameter of type 'string | number'.(2345)
여기서 world 인터페이스를 자세히 살펴보자
interface World{
getItem<T extends string | number>(id: T): T extends string ? Table : Dino;
}
getItem 함수에 제너릭을 받는데 string | number 유니온 타입만을 받고 있어서 위 코드에
boolean 타입을 할당할 경우 에러 발생
Flatten<T>
type Flatten<T> = T extends any[]
? T[number]
: T extends object
? T[keyof T]
: T;
const numbers = [1,2,3];
type NumbersArrayFlattened = Flatten<typeof numbers>; // number
const Person = {
age:23,
name:"king"
}
type SomeObjectFlattened = Flatten<typeof Person>; // string | number
// 추론과정
// 1. keyof T --> "age" | "name"
// 2. T["age" | "name"] --> T["age"] | T["name"] --> number | string
const bool = true;
type SomeBooleanFlattened = Flatten<typeof bool>; // true
Flatten 타입은 이중 삼항연산자로 타입을 추론하는데
1. 배열이면 배열에 아이템 타입을 리턴
위 코드에서 numbers 라는 number 배열을 제너릭에 넘겨줬기에 number를 리턴받는다.
string[]을 넘겨주면 string을 받는다.
2. object 타입이면 object 타입에 키속성을 리턴
Person 객체의 속성은 number 와 string을 가지고 있으므로 string | number를 리턴받느다.
3. 배열이나 객체도 아니라면 원시객체를 리턴
infer ★★★
type UnPackPromise<T> = T extends Promise<infer K>[] ? K : any;
const promises = [Promise.resolve("str"), Promise.resolve(3)]; // (Promise<string> | Promise<number>)[]
type Expected = UnPackPromise<typeof promises>; // string | number
UnPackPromise 타입에 제너릭으로 promises 의 타입을 넘겨준다.
UnPackPromise 제너릭으로 들어온 타입은 (Promise<string> | Promise<number>)[] 이므로
Promise 내부 리턴타입 K를 infer 키워드를 사용하여서 뱉어낸다. 즉 K는 string | number 타입이 된다.
infer 키워드를 사용해서 나만의 함수의 리턴타입을 만들어보자.
function plus1(seed: number): number {
return seed + 1;
}
type MyReturnType<T extends (...args:any) => any> =
T extends (...args: any) => infer R
? R
: any;
type Id = MyReturnType<typeof plus1>;
아까 위에서 함수 파라미터에 제약 사항을 걸어놓았듯이
MyReturnType 에 제너릭 타입에도 함수를 걸어놓았다.
<T extends (...args:any) => any> 즉 T는 반드시 함수여야 한다.
만약에 함수라면 infer 키워드를 사용해서 함수의 리턴타입을 추론한다.
typescript 에 내장된 Utility Type은 다음과 같이 작성할 수 있다.
공식 doc은 여기 있으니 확인해보자.
https://www.typescriptlang.org/docs/handbook/utility-types.html
Documentation - Utility Types
Types which are globally included in TypeScript
www.typescriptlang.org
// type Exclude<T,U> = T extends U ? never : T;
type MyExclude = Exclude<string | number, string>; // number
// type Extract<T,U> = T extends U ? T : never;
type MyExtracted = Extract<string | number, string>; // string -> filter의 기능
// Pick<T , Exclude<keyof T, K>>;
type MyPicked = Pick<{name:string, age:number}, "name">; // {name:string}
// type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type MyOmited = Omit<{name:string , age:number}, "name">;
// type NonNullable<T> = T exteds null | undefined ? never : T;
type MyNonNullable = NonNullable<string | number | undefined | null>; // string | number
타입이나 인터페이스안에서 함수인 속성 다루기
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Person {
id: number;
name: string;
hello: (message: string) => void;
}
type T1 = FunctionPropertyNames<Person>; // "hello"
type T2 = NonFunctionPropertyNames<Person>; // "id" | "name"
type T3 = FunctionProperties<Person>; // { hello: (message: string) => void;}
type T4 = NonFunctionProperties<Person>; // { id: number; name: string;}
하나 하나 살펴보자. 느리게 살펴보면 그렇게 어렵지(?) 않다.
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type T1 = FunctionPropertyNames<Person>; // "hello"
위 타입은 함수인 속성의 이름을 가져오는 타입이다.
제너릭으로 받은 타입의 in keyof 를 사용하여 객체의 키값을 가져오고 함수라면 속성명을 리턴하고 아니라면 never로 사용 하지 못하도록 만든다.
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type T3 = FunctionProperties<Person>; // { hello: (message: string) => void;}
그러므로 함수의 속성명이 아닌 값타입을 얻고 싶다면 Pick 유틸리티 타입을 이용하여 함수의 실제 타입을 얻을 수 있다.
다음편에 계속
'TypeScript' 카테고리의 다른 글
type challange <Easy> (0) | 2022.11.03 |
---|---|
우아한 타입스크립트 실전 코드 작성하기(2) (0) | 2022.06.08 |
여러가지 형태의 타입가드 (0) | 2022.05.09 |
Type vs Interface (0) | 2021.12.05 |
abstract 와 interface 차이점 (0) | 2021.11.08 |
댓글