제네릭
- <타입변수>
- 타입을 마치 함수의 파라미터처럼 사용하는 것
function toObj<T>(a: T, b: T): { a: T; b: T } {
// 재사용가능
return { a, b };
}
toObj<string>("a", "b");
toObj<number>(1, 2);
toObj(true, false);
타입변수를 명시적으로 넣어주지 않아도 된다. 그러나 첫번째 인수로 타입 추론을 하기 때문에 첫번째 인수와 타입이 다르면 에러가 발생한다
function toObj<T>(a: T, b: T): { a: T; b: T } {
// 재사용가능
return { a, b };
}
toObj<string>("a", 12); // 에러 발생
toObj(1, 2); // O
toObj(true, false); // O
제약조건
- <타입변수 extends TYPE>
- 제네릭을 더욱 안정적으로 사용할 수 있다
function toObj<T extends string | number | boolean>(a: T, b: T): { a: T; b: T } {
// string or number or boolean만 허용
return { a, b };
}
함수에서의 제네릭
function 함수명<타입변수>(매개변수 : 매개변수타입) {}
const 화살표함수명 = <타입변수>(매개변수 : 매개변수타입) => {} ;
인터페이스에서의 제네릭
- interface 인터페이스명<타입변수>
interface ToObj<T> {
a: T;
b: T;
}
function toObj<T extends string | number | boolean>(a: T, b: T): ToObj<T> {
return { a, b };
}
타입별칭에서의 제네릭
interface User<T, U, V> {
name: T;
age: U;
isValid: V;
}
const heropy: User<string, number, boolean> = { name: "heropy", age: 30, isValid: true };
const neo: User<string, number, boolean> = { name: "neo", age: 55, isValid: true };
const amy: User<string, number, boolean> = { name: "amy", age: 20, isValid: false };
위의 코드와 같이 객체 형태가 아니라 배열 형태로 바꾸기 위해 타입 별칭이 사용된다.
- 인터페이스에서 배열 타입은 인덱스 시그니처를 사용해야만 하기 때문에 타입 별칭을 사용해준다
- union 타입, 튜플타입(배열의 안쪽에 아이템의 타입 선언) 사용
type User<T, U, V> = { name: T; age: U; isValid: V } | [T, U, V];
const evan: User<string, number, boolean> = ["evan", 77, true];
const lewis: User<string, number, boolean> = ["lewis", 40, false];
const leon: User<string, number, boolean> = ["leon", 33, true];
위의 코드 중에서 반복되는 코드를 재활용하여 효율적으로 관리할 수 있다
type User<T, U, V> = { name: T; age: U; isValid: V } | [T, U, V];
type U = User<string, number, boolean>; // 반복되는 부분 재활용
const heropy: U = { name: "heropy", age: 30, isValid: true };
const neo: U = { name: "neo", age: 55, isValid: true };
const amy: U = { name: "amy", age: 20, isValid: false };
const evan: U = ["evan", 77, true];
const lewis: U = ["lewis", 40, false];
const leon: U = ["leon", 33, true];
클래스에서의 제네릭
class Basket<T> {
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("Apple", "Banana", "Cherry");
// 명시적으로 타입변수를 선언하지 않아도 들어오는 데이터의 타입으로 타입추론 가능(string)
fruitsBasket.putItem("Orange");
const fruits = fruitsBasket.takeOutItems(2);
console.log(fruits); // ["Orange", "Apple"]
console.log(fruitsBasket.items); // ["Banana", "Cherry"]
const moneyBasket = new Basket(100, 1000, 10000);
// 명시적으로 타입변수를 선언하지 않아도 들어오는 데이터의 타입으로 타입추론 가능(number)
moneyBasket.putItem(40000);
const money = moneyBasket.takeOutItems(2);
console.log(money); // [50000, 100]
console.log(moneyBasket.items); // [1000, 10000]
제약조건
문자데이터만이 들어가는 구조로 사용하려면 extends라는 키워드를 사용해 제약조건을 만든다
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);
}
}
다음과 같이 제약조건을 만들면 fruitsBasket.putItem("Orange") 에서 에러가 발생한다
- 그 이유는 extends를 통해 타입변수에 제약조건을 추가하게 되면 타입스크립트가 변수의 타입을 추론을 할 때 최대한 구체적으로 타입을 추론을 한다. 따라서 들어오는 데이터의 타입이 같아도 데이터 값이 다르면 에러가 발생한다
에러를 해결하기 위해 타입변수를 명시적으로 선언해준다
const fruitsBasket = new Basket<string>("Apple", "Banana", "Cherry");
// string 타입인 모든 데이터 허용
const moneyBasket = new Basket<number>(100, 1000, 10000);
// 위에서 string 타입으로 먼저 선언이 되었기 때문에 에러발생
제네릭의 조건부 타입
- 유틸리티타입을 만들거나, 내장 유틸리티 타입을 해석할때 쓰인다
- 삼항연산자를 사용한다
type MyType<T> = T extends string | number ? boolean : never;
const a: MyType<string> = true; // string => boolean
const b: MyType<number> = true; // number => boolean
const c: MyType<null> = true; // string 또는 number이 아니라 never 타입이어야 한다
type MyExclude<T, U> = T extends U ? never : T;
// string | number | boolean | null extends boolean | null ? never : string | number | boolean | null
type MyUnion = string | number | boolean | null;
const d: MyExclude<MyUnion, boolean | null> = 123;
// string | number -> T (string | number)
// boolean | null -> never
위와 같이 특정한 타입을 만들어서 필요한 경우 활용하는 경우는 유틸리티 타입이라고 한다.
그런데, 위 코드의 유틸리티 타입 부분은 이미 내장에 있다 (Exclude 타입)
const d: Exclude<MyUnion, boolean | null> = 123;
type IsPropertyType<T, U extends keyof T, V> = T[U] extends V ? true : false;
type Keys = keyof User;
interface User {
name: string;
age: number;
}
const n: IsPropertyType<User, "name", number> = true;
// User의 name 타입이 number인지 확인
// User["name"] -> string이므로 error
keyof 연산자 : 객체 형태의 타입을, 따로 속성들만 뽑아 모아 유니온 타입으로 만들어주는 연산자
제네릭의 infer
- infer : 추론하다
- 조건부 타입에서 타입을 추론할 때 사용한다
- infer키워드를 통해 타입변수를 정의할건데 타입변수가 처음 만들어진 자리에서 타입변수를 추론해서 어떤 타입인지 알수 있는지 없는지 확인
type ArrayItemType<T> = T extends (infer I)[] ? I : never;
// infer I -> 타입추론을 통해 I의 타입을 추론
const numbers = [1, 2, 3];
const a: ArrayItemType<number[]> = 123;
// infer I의 타입은 number[]로 추론할 수 있다
const b: ArrayItemType<boolean> = 123;
// error => 추론된 타입이 number[]인데 변수타입이 boolean이어서 추론된 타입과 다르기 때문에 never 타입이어야 한다
type ArrayItemType<T> = T extends (infer I)[] ? I : never;
// infer I -> 타입추론을 통해 I의 타입을 추론
const fruits = ["Apple", "Banana", "Cherry"];
const hello = () => {};
const c: ArrayItemType<typeof fruits> = "ABC";
// typeof fruits -> string[]
const d: ArrayItemType<typeof hello> = "ABC";
// typeof hello -> () => void
// hello의 타입은 함수이고 infer I는 배열이기 때문에 타입 추론을 할 수 없어 d는 never타입이어야 함
함수 인자에서 infer
type SecondArgumentType<T> = T extends (f: any, s: infer S) => any ? S : never;
// (a:string, b:number) => void extends (f:any, s: infer S) => any ? S : never
// 비교하면 infer S -> number로 추론아 되어서 true를 반환하고 S는 number타입이 됨
function hello(a: string, b: number) {}
const a: SecondArgumentType<typeof hello> = 123;
// typeof hello -> (a:string, b:number) => void
ReturnType 유틸리티타입
- 함수 Type의 반환 타입으로 구성된 타입을 생성
type MyReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
// 함수 타입만 사용할 수 있고 함수의 인자의 타입을 반환한다
// MyReturnType은 유틸리티 타입인 ReturnType과 같은 기능을 한다
function add(x: string, y: string) {
return x + y;
}
const a: ReturnType<typeof add> = "Hello";
// typeof add -> (x:string, y:string) => string
'📘 TypeScript' 카테고리의 다른 글
TypeScript - 추상 클래스 (0) | 2023.11.22 |
---|---|
TypeScript - 클래스의 접근 제어자 (0) | 2023.11.21 |
TypeScript - 함수의 오버로딩 (0) | 2023.11.21 |
TypeScript - 함수의 명시적 this 타입 (0) | 2023.11.21 |
TypeScript - 인터페이스(Interface) (1) | 2023.11.20 |