
function toObj(a: string, b: string): { a: string; b: string }
function toObj(a: number, b: number): { a: number; b: number }
function toObj(a: boolean, b: boolean): { a: boolean; b: boolean }
function toObj(a: string | number | boolean, b: string | number | boolean): { a: string | number | boolean; b: string | number | boolean} {
return { a, b };
}
toObj('A', 'B');
toObj(1, 2);
toObj(true, false);
위 코드처럼 들어온 매개변수들을 객체로 만들어 반환하는 매우 간단한 코드에서도 들어오는 매개변수가 모두 string, number, boolean 으로 지정해 줘야 한다면 배웠던 오버로딩 을 사용하여 구현하면 선언부의 코드가 매우 길어지는 걸 볼 수 있다...
<T>라는 부분을 타입변수라고 한다. 타입을 내부에서 재사용 가능extends string | number | boolean 이 부분의 코드처럼 제약조건을 넣어줌으로써 어떤 타입만 가질 수 있는지 정의할 수 있다.function toObj<T extends string | number | boolean>(a: T, b: T): { a: T; b: T } {
return { a, b };
}
toObj('A', 'B');
toObj<number>(1, 2);
toObj<boolean>(true, false);
toObj(null,null) // Error
toObj('A', 'B')이 부분에서 <>가없어도 되는 이유는 타입을 호출하는 부분에서 첫번째 인자가 string, number 등의 값을 가지고 있으므로<T>에는 첫번째 인자의 type이 정의되고 그 뒤에 코드는 자연스럽게 그 값으로 정의되게 된다!!! 뒤에 코드들도 명시적으로 작성해 줄 필요는 없다.
interface ToObj<T> {
a: T;
b: T;
}
function toObj<T extends string | number | boolean> (a: T, b: T) : ToObj<T>{
return {a,b}
}
type User<T, U, V> = {
name : T
age : U
isValid : V
} | [T,U,V]; // 튜플타입
const jth: User<string, number, boolean> = {
name: 'JTH',
age: 25,
isValid: true,
};
const neo: User<string, number, boolean> = {
name: 'NEO',
age: 10,
isValid: true,
};
const prodo: User<string, number, boolean> = {
name: 'PRODO',
age: 40,
isValid: false,
};
const a: User<string, number, boolean> = ['A', 40, false];
const b: User<string, number, boolean> = ['B', 40, false];
const c: User<string, number, boolean> = ['C', 40, false];
type User<T, U, V> = {
name : T
age : U
isValid : V
} | [T,U,V]; // 튜플타입
type U = User<string,number,boolean>
const jth: U = {
name: 'JTH',
age: 25,
isValid: true,
};
const neo: U = {
name: 'NEO',
age: 10,
isValid: true,
};
const prodo: U = {
name: 'PRODO',
age: 40,
isValid: false,
};
const a: U = ['A', 40, false];
const b: U = ['B', 40, false];
const c: U = ['C', 40, false];
type T = 'Apple' | 'Banana' | 'Mango' // 제약조건이 걸리면서 이렇게 추론함
class Basket<T extends string> {
public items: T[]
constructor(...rest: T[]){
this.items = rest
}
putItem(item: T) {
this.items.unshift(item)
}
takeOutItems(count: number){
return this.items.splice(0, count)
}
}
const fruitsBasket = new Basket<string>('Apple',"Banana","Mango")
fruitsBasket.putItem('Cherry') // 명시적으로 타입을 지정해주면서 에러 해결
const fruits = fruitsBasket.takeOutItems(2)
console.log(fruits) //['Cherry', 'Apple']
console.log(fruitsBasket.items) //['Banana','Mango']
📌 주의할 점 : 제약조건을 걸게되면 타입스크립트는 최대한 구체적이고 특정한 타입으로 추론하려고 하기 때문에 Apple,Banana,Mango 타입이 되어버린다. 이럴 때는 명시적으로 타입을 지정해 줄 필요가 있다!!
type MyType<T> = T extends string | number ? boolean : never;
const a: MyType<string> = true;
const b: MyType<number> = true;
const c: MyType<null> = true; //never 타입이여서 Error 발생
type MyExclude<T, U> = T extends U ? never : T; //유틸리티 타입
type MyUnion = string | number | boolean | null;
const a: MyExclude<MyUnion, boolean | null> = 123; // 문자이거나 숫자여야한다 string | number
const a: Exclude<MyUnion, boolean | null> = 123;
위의 예제에서는 (Utility Type) 내장이 된 타입(Exclude)이 존재하여 직접 만들어 줄 필요없이
Exclude로 사용해도 동작한다.
keyof : 키 밸류 형태에서 키 부분만 추출해서 유니온 타입으로 만들어준다.type IsPropertyType<T, U extends keyof T, V> = T[U] extends V ? true : false; //T[U] -> User['name'] false 타입
type Keys = keyof User; // 'name' | 'age'
interface User {
name: string;
age: number;
}
const result: IsPropertyType<User, 'name', number> = true; //Error
name 속성은 number 타입이 아니므로 결국 false 값이 반환되고 위 예제에서는 true라고 선언했으므로 Error가 나타나게 된다!!
extends 키워드를 기준으로 왼쪽의 구조와 지금 현재 infer쪽의 구조를 잘 살펴보자.// number[] extends (infer I)[] -> number 타입이 추론됨
type ArrayItemType<T> = T extends (infer I)[] ? I : never;
const numbers = [1, 2, 3];
const a: ArrayItemType<typeof numbers> = 123; //<typeof numbers> -> number[]
const b: ArrayItemType<boolean> = 123;
const fruits = ['Apple', 'Mango', 'Orange'];
const hello = () => {};
const c: ArrayItemType<typeof fruits> = 'abc'; //string[]
const d: ArrayItemType<typeof hello> = 'abc'; //Error never 타입
// typeof hello -> () => void
즉 infer 키워드를 통해 타입 변수를 정의할껀데 어떤 타입인지 알 수 있는지 없는지 알아보기 -> 타입추론이 가능한지 아닌지
type SecondArgumentType<T> = T extends (f: any, s: infer S) => any ? S : never;
function hello(a: string, b: number) {}
const result: SecondArgumentType<typeof hello> = 123; // number 타입
//(a: string,b: number) => void
// 제약조건 : 함수 데이터여야 한다
type MyReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R ? R : any;
function add(x: string, y: string) {
return x + y;
}
const result: ReturnType<typeof add> = 'Hello'; //string 타입이 됨
// (x: string, y: string) => string