logo
Published on

Typescript Generic

Authors
  • avatar
    Name
    Geurim
    Twitter

타입스크립트 제네릭

제네릭이란?

제네릭(Generics)은 타입스크립트에서 함수, 클래스, 인터페이스 등을 작성할 때 여러 타입에 대해 재사용 가능한 컴포넌트를 만들 수 있게 해주는 기능입니다. 제네릭을 사용하면 특정 타입에 종속되지 않고 다양한 타입에 대해 동작하는 컴포넌트를 만들 수 있습니다. 바로 예시코드를 보면서 익숙해져 보겠습니다.

제네릭은 함수에서 인자를 받는 것 처럼 타입 인자를 받을 수 있습니다.

//          v---v 인자 식별자
const eat = (food: FoodType) => {...}
//                 ^-------^ 인자 타입 지정
//       v------v 타입 인자 식별자
type EAT<FoodType> = (food: FoodType) => {...}

타입 인자 식별자에는 제약 조건이 없습니다. 따라서 이렇게 코드를 작성한다면,

type Food<T> = {
  name: 'chicken'
  price: 20000
  description: T
}

다음과 같이 어떤 타입이든 들어올 수 있습니다.

type BoolFood = Food<boolean>
type StringFood = Food<string>
type VoidFuncFood = Food<() => void>

아래 예시의 identity 함수는 어떤 타입의 인자라도 받아서 그대로 반환합니다. <T> 는 타입변수로, 실제 사용시 구체적인 타입으로 대체됩니다.

function identity<T>(arg: T): T {
  return arg
}
let output1 = identity<string>('myString') // 타입은 'string'
let output2 = identity(123) // 'number'

이를 활용한다면 아래 예시의 Box interface는 어떤 타입의 contents든 가질 수 있습니다.

interface Box<T> {
  contents: T
}

const box1: Box<string> = { contents: 'hello world' }
const box2: Box<number> = { contents: 42 }

조금 더 제네릭을 잘 활용한다면 다음과 같이 하나의 큐를 만들어서 여러 타입의 큐로 사용할 수도 있습니다.

class Queue<T> {
  private queue: T[] = []

  push(item: T) {
    this.queue.push(item)
  }

  pop(): T | undefined {
    return this.queue.shift()
  }
}

const numQueue = new Queue<number>()
numQueue.push(10)
console.log(numQueue.pop()) // 10

const strQueue = new Queue<string>()
strQueue.push('Hello')
console.log(strQueue.pop()) // 'Hello'

하지만 일반적으로 컴포넌트는 어떤 타입이든 받아도 되는 컴포넌트가 아닐 가능성이 큽니다. 따라서 제네릭 타입에 제약조건을 설정해야 합니다. 그럴때는 extends 키워드를 사용할 수 있습니다.

interface LengthWise {
  length: number
}

function LoggingIdentity<T extends LengthWise>(arg: T): T {
  console.log(arg.length) // length 속성이 있다고 확신할 수 있습니다.
  return arg
}

LoggingIdentity(3) // error
LoggingIdentity({ length: 10, value: 20 }) // ok

제네릭 타입의 키워드는 ’T’, ‘U’ 를 주로 일반적으로 자주 사용하는데, 명시적으로 작성해주는게 좋습니다. 또한 제네릭 타입은 함수 인자와 마찬가지로 하나 이상의 타입을 인자로 받을 수 있습니다.

type Message<Title, Content = 'Hello'> = {
  title: Title
  content: Content
}

type ImportantMessage = Log<'Important', 'this message is important'>
type DefaultMessage = Log<'Hello'>