1. 개요

다양한 타입이 들어올 수 있도록 유연성을 제공하는 문법.
예를 들어 다음과 같이 작성한다.

function identity<T>(arg: T): T {
  return arg;
}
 
let output1 = identity('txt');  // type: string
let output2 = identity(12345);  // type: number
let output3 = identity(true);  // type: boolean
class ItemStorage<T> {
  private storage: T[] = [];
  
  add(item: T) {
    this.storage.push(item);
  }
  remove(item: T) {
    this.storage.filter((value) => item !== value);
  }
  show() {
    return [...this.storage];
  }
}
 
const stringStorage = new ItemStorage<string>();

2. 상세

2.1. union type과의 차이점

generics의 특성은 다음과 같다.

  • 여러 타입을 허용하되, 사용시점에 구체적인 타입이 지정된다.
  • 한 개의 함수에 다양한 타입을 사용할 수 있으므로 재사용성이 증가한다.
  • 사용시점에 정해진 타입을 그대로 유지한다.

union type의 특성은 다음과 같다.

  • 제시된 타입들 중 한 개를 선택할 수 있다.
  • union type에 포함된 타입들의 공통 속성만 접근 가능하다.

genericunion type을 상호배타적 관계로 생각하면 안 된다.
둘은 각각의 목적을 가지며, 둘을 함께 조합해서 사용할 수도 있다.

예시를 살펴보자.
아래 코드는 generic으로 들어올 수 있는 타입을 extends 키워드를 이용하여 numberstring으로 제한하였다.
따라서 boolean 타입의 인자를 넣었을 때 에러가 발생한다.

function identity<T extends number | string>(arg: T): T {
  return arg;
}
 
let output1 = identity('sample'); // type: string
let output2 = identity(12345678); // type: number
let output3 = identity(true); // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.

2.2. keyof 제약

keyof로도 타입을 제약할 수 있다.
객체와 객체의 key를 인자로 넣으면, 해당 key에 대한 value를 반환하는 함수가 있다고 가정해보자.

const sampleObject = {
  name: 'YU',
  address: 'Seoul'
};
 
const getProperty = (obj: object, key: string) => {
  return obj[key];
};
 
getProperty(sampleObject, "name");  // "YU"
getProperty(sampleObject, "age");  // undefined

이 함수에서 발생할 수 있는 문제점은, 객체에 정의되어있지 않은 key를 인자로 넣어도 에러가 발생하지 않는다는 것이다.
이 문제는 keyofgeneric을 조합하여 해결할 수 있다.

const sampleObject = {
  name: 'YU',
  address: 'Seoul'
};
 
const getProperty = <T extends object, U extends keyof T>(obj: T, key: U) => {
  return obj[key];
};
 
getProperty(sampleObject, "name");  // "YU"
getProperty(sampleObject, "age");  // Argument of type '"age"' is not assignable to parameter of type '"name" | "address"'.
 

generic은 사용시점에서 구체적인 타입이 지정되므로, 매개변수 key의 타입은 인자로 들어온 객체의 모든 key값이 되며, 이에 따라 타입 검사도 함께 이루어져 올바른 인자를 넣을 수 있게 된다.

3. utility type

Partial

T의 모든 속성을 선택적으로 만든다.

Partial<T>

Readonly

T의 모든 속성을 읽기 전용으로 만든다.

Readonly<T>

Required

T의 모든 속성을 필수로 만든다.

Required<T>

Pick

T에서 K 속성만 선택한다.

Pick<T, K>

Omit

T에서 K 속성을 제외한 나머지를 선택한다.

Omit<T, K>

Record

키 타입 K와 값 타입 T를 가진 객체 타입을 생성한다.

Record<K, T>

예시는 다음과 같다.

type Fruit = "apple" | "orange" | "banana";
type FruitCount = Record<Fruit, number>;
 
const data: FruitCount = {
  apple: 1,
  banana: 2,
  orange: 3,
};
interface Person {
  name: string;
  age: number;
}
 
type Team = "HR" | "Accounting" | "dev";
type TeamRoster = Record<Team, Partial<Person>>;
 
const teams: TeamRoster = {
  Accounting: { name: "kim" },
  dev: { name: "lee", age: 27 },
  HR: { age: 20 },
};