제네릭 (extends, infer)

2022. 12. 26. 16:44프로그래밍/TypeScript

제네릭 (Generic)

재사용을 목적으로 함수나 클래스의
사용 시점에 타입을 선언하는 방법
// T는 타입 변수
// 사용자가 제공한 타입으로 변환될 식별자
// 다른 이름으로 변경 가능
function toArray<T>(a: T, b: T): T[] {
  return [a, b];
}

toArray<number>(1, 2);
toArray<string>('1', '2');
toArray<string | number>(1, '2');
toArray<number>(1, '2'); // Error. number 타입만 인수로 전달 가능

// 타입 추론 활용
// 사용 시점에 인수 타입을 제공하지 않을 수도 있음
toArray(1, 2);
toArray('1', '2');
toArray(1, '2') // Error. 두 타입은 같아야 함

 

제약 조건 (Constraints)

인터페이스/타입 별칭을 사용하는 제네릭 작성 가능
extends 키워드를 사용해 제약 조건 추가 가능
T extends U
// 타입변수 T는 string, number 타입만 허용함 
// extends 키워드 사용
interface MyType<T extends string | number> {
  name: string,
  value: T
}

const dataA: MyType<string> = {
  name: 'Data A',
  value: 'Hello world'
}
const dataB: MyType<number> = {
  name: 'Data B',
  value: 1234
}
const dataC: MyType<boolean> = { // Error. string, number 타입만 가능
  name: 'Data C',
  value: true
}
const dataD: MyType<number[]> = { // Error. string, number 타입만 가능
  name: 'Data D',
  value: [1, 2, 3, 4]
}
type U = string | number | boolean;

// type 식별자 = 타입 구현
type MyType<T extends U> = string | T;
const t: MyType<number> = '1';

// interface 식별자 { 타입 구현 }
interface User<T extends U> {
  name: string,
  age: T
}
const u: User<number> = {
  name: 'Heropy',
  age: 85
};

 

조건부 타입 (Conditional Types)

타입 구현 영역에서 사용하는 extends 키워드
삼항 연산자 사용
T extends U ? X : Y
type U = string | number | boolean;

// type 식별자 = 타입 구현
type MyType<T> = T extends U ? string : never;

// interface 식별자 { 타입 구현 }
interface IUser<T> {
  name: string,
  age: T extends U ? number : never;
}
// T는 boolean 타입으로 제한
interface IUser<T extends boolean> {
  name: string,
  // T의 타입이 true인 경우 string 반환
  // 아닌 경우 number 반환
  age: T extends true ? string : number, 
  isString: T
}

const str: IUser<true> = {
  name: 'Neo',
  age: '12', // String
  isString: true
}
const num: IUser<false> = {
  name: 'Lewis',
  age: 12, // Number
  isString: false
}

 

infer

타입 변수의 타입 추론 여부 확인 가능
infer 키워드 사용
T extends infer U ? X : Y
U 가 추론 가능한 타입이면 true, 아니면 false
// 타입 변수 R은 MyType<number>에서 받은 타입 number가 됨
// number타입은 타입 추론이 가능하므로 R을 반환함
type MyType<T> = T extends infer R ? R : null;

// MyType<number>는 number를 반환함
// 변수 a는 123을 할당할 수 있음
const a: MyType<number> = 123;
// 타입 변수 R은 string
// string타입은 타입 추론이 가능하므로 R을 반환함
// ReturnType는 함수의 반환 값이 어떤 타입인지 반환함
type ReturnType<T extends (...args: any) => any> = 
  T extends (...args: any) => infer R ? R : any;

// (num: number) => string
function fn(num: number) {
  return num.toString();
}

// typeof fn은 string
// ReturnType<typeof fn>은 string 할당 가능
const a: ReturnType<typeof fn> = 'Hello';
  • 제약 조건 extends가 아닌 조건부 타입 extends 절에서만 사용 가능
  • 같은 타입 변수를 여러 위치에서 사용 가능
  • 여러 호출 시그니처(함수 오버로드)의 경우 마지막 시그니처에서 추론