TypeScript - 제네릭

2023. 11. 22. 21:15· 📘 TypeScript
목차
  1. 제네릭
  2. 제네릭의 조건부 타입
  3. 제네릭의 infer

제네릭

  • <타입변수>
  • 타입을 마치 함수의 파라미터처럼 사용하는 것
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
  1. 제네릭
  2. 제네릭의 조건부 타입
  3. 제네릭의 infer
'📘 TypeScript' 카테고리의 다른 글
  • TypeScript - 추상 클래스
  • TypeScript - 클래스의 접근 제어자
  • TypeScript - 함수의 오버로딩
  • TypeScript - 함수의 명시적 this 타입
zunwon
zunwon
zunwon
준원의 개발일지
zunwon
전체
오늘
어제
  • 분류 전체보기
    • 📒 JavaScript
    • 📘 TypeScript
    • React.js
    • 📗 Vue.js
    • 💻 알고리즘
      • 프로그래머스
      • 백준
    • ✏ 회고
      • 후기
    • 👨‍💻 TIL
    • 📋 오픈소스
    • 기타

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 함수형자바스크립트
  • 데브코스
  • Vue
  • githru
  • 스나이퍼팩토리
  • 미래내일일경험
  • next.js
  • 프론트엔드
  • mil
  • css
  • 국비지원교육
  • 자료구조
  • fontend
  • Vue.js
  • 회고
  • frontend
  • TypeScript
  • 오픈소스컨트리뷰션
  • JavaScript
  • 인사이드아웃
  • udemy
  • 프로젝트캠프
  • 프로그래머스
  • 프로그래머스 데브코스
  • 코딩부트캠프
  • til
  • 함수형 자바스크립트
  • 부스트캠프
  • vercel
  • 알고리즘

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.1
zunwon
TypeScript - 제네릭
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.