WEB/TypeScript

[TypeScript] 제네릭 (함수/클래스/인터페이스, 제약 조건)

devOhzl 2024. 8. 26. 23:12

제네릭 (Generic)

함수, 클래스, 인터페이스에서 사용 가능하다.

타입 변수 부분에 제약 조건도 활용 가능하다.

함수

함수 이름 뒷 쪽에 대문자 T 로 타입 변수를 지정을 할 수 있다.

함수를 호출할 때 타입을 넣어줄 수도 있지만, 첫번째 인수를 통해 타입추론이 가능하게 사용할 수도 있다.

 

예제로 함수에서 제네릭 문법 사용하기

interface Obj {
  x: number
}
type Arr = [number, number]

function toArray(a: string, b: string): string[]
function toArray(a: number, b: number): number[]
function toArray(a: boolean, b: boolean): boolean[]
function toArray(a: Obj, b: Obj): Obj[]
function toArray(a: Arr, b: Arr): Arr[]
function toArray(a: any, b: any) {
  return [a,b]
}

console.log(
  toArray('Neo', 'Anderson'),  //[ 'Neo', 'Anderson' ]
  toArray(1,2),    // [ 1, 2 ] 
  toArray(true, false),  // [ true, false ]
  toArray({ x:1 }, { x:2 }),  // [ { x: 1 }, { x: 2 } ]
  toArray([1,2],[3,4])  // [ [ 1, 2 ], [ 3, 4 ] ]
)

위의 예제를 toArray를 함수를 다르게 사용할 때마다 오버로딩을 시킬 수는 없으므로 제너릭 문법으로 개선한다.

 

interface Obj {
  x: number
}
type Arr = [number, number]

function toArray<T>(a: T, b: T) {
  return [a,b]
}

console.log(
  toArray('Neo', 123),  // error! 알 수 없는 T 타입이 string이 되었다
  toArray<string>('Neo',123),  // error! T 타입을 string으로 지정하였다.
  toArray(1,2),    // [ 1, 2 ] 
  toArray<number>(1,2),    // [ 1, 2 ] 
  toArray(true, false),  // [ true, false ]
  toArray({ x:1 }, { x:2 }),  // [ { x: 1 }, { x: 2 } ]
  
  toArray([1,2],[3,4,5])  // [ [ 1, 2 ], [ 3, 4, 5 ] ] , number[] 이 되므로 에러 발생 X
  toArray<Arr>([1,2],[3,4,5]) // error! 튜플 타입이 되므로 5를 제거해야됨
)

타입 추론은 최대한 활용하는 것이 좋으므로 <string> 이나 <number>은 생략 가능하다.

 

클래스

클래스의 이름 뒤에 <> 를 열고 닫아 타입 별칭을 지정한다.

class User<P> {
  constructor(public payload: P)
  getPayload() {
    return this.payload
  }
}

/* 
위의 코드는 아래의 코드 정리한 것임
class User<P> {
  public payload: P
  constructor(payload: P){
    this.payload = payload
  }
  getPayload() {
    return this.payload
  }
}
*/

User 클래스 생성자를 호출하면 특정한 타입 P를 받는다,

payload 는 매개변수이면서 내부 속성이므로 this 키워드로 접근 가능하며, 타입을 지정한다.

 

클래스에서 제네릭 문법을 사용해보자

class User<P> {
  constructor(public payload: P)
  getPayload() {
    return this.payload
  }
}

interface UserAType {
  name: string
  age: number
  isValid: boolean
}

interface UserBType {
  name: string
  age: number
  emails: string[]
}

const ohzl = new User<UserAType>({
  name: 'ohzl',
  age: 27,
  isValid: true,
  emails: []    // error! UserAType과 일치하지 않으므로 할당 불가능
})

const ohzl2 = new User<UserBType>({
  name: 'ohzl',
  age: 27,
  emails: ['kar2125@naver.com']    
})

 

인터페이스

사용방법은 클래스와 같다.

interface MyData<T> {
  name: string
  value: T
}

const dataA: MyData<string> = {
  name: 'Data A',
  value: 'Hello world'
}

const dataB: MyData<number> = {
  name: 'Data B',
  value: 1234
}

const dataC: MyData<boolean> = {
  name: 'Data C',
  value: true
}

const dataD: MyData<number[]> = {
  name: 'Data D',
  value: [1,2,3,4]
}

 

제약 조건(Constraints)

타입 변수에 제약 조건을 넣을 수 있다.

타입 변수 T에 허용할 수 있는 타입을 지정한다.

interface MyData<T extends string | number[]> {
  name: string
  value: T
}

const dataA: MyData<string> = {
  name: 'Data A',
  value: 'Hello world'
}

const dataB: MyData<number> = {	  // error! 타입에는 sting이나 number만 가능하다.
  name: 'Data B',
  value: 1234
}

const dataC: MyData<boolean> = {  // error! 타입에는 sting이나 number만 가능하다.
  name: 'Data C',
  value: true
}

const dataD: MyData<number[]> = { 
  name: 'Data D',
  value: [1,2,3,4]
}