12. 제네릭 (1)

2021. 11. 11. 23:10재주껏 하는 Front-End/타입스크립트 (준비중)

반응형

이번 글에서는 타입스크립트의 제네릭에 대해 알아보자. 제네릭은 많은 사람들이 가장 헷갈려하고 어려워하는 문법으로 정적 타입 언어에서 코드의 다양성을 구현하기 위해 필요한 필수 문법이다. 지금부터 가장 어려운 문법인 제네릭에 대해 살펴보자.

 

1. 제네릭이란?

 

제네릭이란 컴포넌트 모듈을 만들 때 가장 많이 사용하는 문법으로 여러 가지 타입을 대응하기 위한 일종의 템플릿 문법이다. 이미 다른 객체 지향 언어에서는 많이 사용되고 있지만, 동적 타입 언어인 자바스크립트에서는 템플릿 문법이 필요하지 않아 제네릭이 지원되지 않았다. (타입이 동적으로 결정되는 자바스크립트 특성상 전혀 필요 없는 문법이기도 하다.)

 

하지만 정적 타입 언어를 지향하는 타입스크립트의 경우에는 함수, 클래스, 인터페이스에서 여러 타입을 대응해야 하는 일이 발생할 수밖에 없기 때문에 제네릭 문법이 추가되었다. 제네릭은 <Type> 형태로 대상 함수, 클래스, 인터페이스 뒤에 명시하여 여러 타입을 대응할 수 있다는 것을 컴파일러에게 알려줄 수 있다.

 

2. 제네릭 기본 문법

 

제네릭은 아래와 같은 방법으로 선언하여 사용한다. 함수, 클래스, 인터페이스 이름 뒤에 "<T>"를 선언하고, 파라미터나 멤버에 T로 타입을 명시한다. T는 Template의 약자로 어떠한 타입이든 들어올 수 있다.

 

// 함수에서 사용하는 경우
function exampleFunc <T> (text: T): T {
  return T
}

// 클래스에서 사용하는 경우
class ExampleClass <T> {
  clsField1: number;
  clsField2: string;
  clsField3: boolean;
  clsField4: T;
}

// 인터페이스에서 사용하는 경우
interface ExampleInterface <T> {
  property1: number;
  property2: string;
  property3: boolean;
  property4: T;
}

 

제네릭은 아래와 같이 사용한다.

 

// 함수에서 사용하는 경우
function exampleFunc <T> (text: T): T {
  return T
}

// 클래스에서 사용하는 경우
class ExampleClass <T> {
  clsField1: number;
  clsField2: string;
  clsField3: boolean;
  clsField4: T;
}

// 인터페이스에서 사용하는 경우
interface ExampleInterface <T> {
  property1: number;
  property2: string;
  property3: boolean;
  property4: T;
}

// 제네릭 함수 호출
exampleFunc <string> ('Malibu')

// 제네릭 클래스로 객체 생성 및 사용
const newCls = new ExampleClass <string> ()

newCls.clsField1 = 10
newCls.clsField2 = 'K5'
newCls.clsField3 = true
newCls.clsField4 = 'SM6'

// 제네릭 인터페이스로 타입 선언
interface Sample {
    property: string;
}

// 새로 생성한 인터페이스도 제네릭으로 지정할 수 있음
const exampleEth : ExampleInterface <Sample> = {
    property1: 10,
    property2: 'Malibu',
    property3: false,
    property4: {
        property: 'chevy'
    }
}

 

위와 같이 함수, 클래스, 인터페이스를 사용할 때 개발자가 원하는 타입으로 정의할 수 있다.

 

3. 제네릭이 필요한 경우

 

그렇다면 제네릭이 필요한 경우는 어떤 것들이 있을까? 제네릭이 유용하게 사용되는 기본 예제를 확인해보자.

 

같은 기능을 하지만 파라미터의 구성에 따라 기능이 달라져야 하는 경우를 생각해보자. 아래의 예제의 코드를 보면 두 함수의 몸체의 내용은 동일하다. 하지만 각 함수의 리턴되는 결과물은 전혀 다르다. 이 경우 여러 개의 함수로 쪼개는 것보다는 하나의 함수로 통일하는 것이 코드의 가독성이나 유지보수에 더 좋을 것이다.

 

function exampFunction (param: string[]) : string[] {
    const resList : string[] = []
    
    for (const item of param) {
        resList.push(item)
    }

    return resList
}

function exampFunction2 (param: number[]) : number[] {
    const resList : number[] = []
    
    for (const item of param) {
        resList.push(item)
    }

    return resList
}

 

위의 코드를 이전에 배웠던 유니온 타입으로 하나의 함수로 합쳐보자. 유니온에 대한 기본 개념은 아래의 글에서 확인할 수 있다.

 

https://kim1124.tistory.com/132?category=232829 

 

8. 기타 타입 (1)

이번 글에서는 타입 정의에 대한 기타 문법에 대해 알아보자. 1. 유니온 앞에서 알아본 타입스크립트의 타입들은 변수 하나에 하나의 타입만 표기할 수 있었다. 하지만 코딩을 하다 보면 특정 변

kim1124.tistory.com

 

아래의 코드는 유니온 타입으로 하나로 합친 코드이다.

 

function exampFunction (param: string[] | number[]) : string[] | number[] {
    const resList = []

    for (const item of param) {
        resList.push(item)
    }

    return param
}

console.log('Items > ', exampFunction([1, 2, 3]), exampFunction(['A', 'B', 'C']))

 

실행 결과는 아래와 같다.

 

 

위의 코드는 우리가 생각한 것처럼 숫자형 배열과 문자열 배열을 모두 대응할 수 있는 것으로 확인하였다. 하지만, 유니온 타입을 사용하니 코드가 필요 이상으로 길어지고 가독성이 떨어지는 문제를 가지고 있다. 이것을 제네릭을 사용하여 간단하게 바꿔보자.

 

ffunction exampFunction <T> (param: T[]) : T[] {
    const resList = []

    for (const item of param) {
        resList.push(item)
    }

    return param
}

console.log('Items > ', exampFunction<number>([1, 2, 3]), exampFunction<string>(['A', 'B', 'C']))

 

실행 결과는 위와 동일하다.

 

 

유니온을 사용한 코드와 달리 제네릭을 사용한 코드는 매우 깔끔하게 정리가 된 것을 볼 수 있다. 위와 같이 제네릭을 사용하면 여러 타입에 대응할 수 있는 재사용성이 높은 코드를 깔끔하게 작성할 수 있다.

 

4. 제네릭 사용 시 주의사항

 

4-1. 제네릭 요소 사용

 

제네릭은 기본적으로 템플릿을 사용하기 때문에 제네릭으로 생성된 함수, 클래스, 인터페이스는 사용 시 반드시 타입을 정의해줘야만 한다. 예를 들어, 제네릭 함수의 경우 아래와 같이 <type> 부분에 타입을 넣어줘야 한다.

 

exampFunction <number> ([1, 2, 3])

 

4-2. 제네릭 함수에서 연산자 사용 X

 

제네릭 함수에서 파라미터의 타입을 제네릭으로 정의한 경우, 타입스크립트의 컴파일러는 함수가 호출될 때 어떤 타입이 들어올지 예측할 수 없으므로 제네릭 파라미터끼리 연산자를 사용할 수 없다.

 

function exampFunction <T> (param: T, param2: T) : T{
    return param + param2
}

 

위의 코드는 VS Code에서 아래와 같은 에러가 발생한다.

 

 

4-3. 여러 개의 탬플릿이 필요한 경우

 

제네릭으로 지정해야 할 멤버가 두 가지 이상일 경우, 아래와 같이 여러 개의 탬플릿을 정의하여 사용할 수 있다.

 

function exampFunction <T, Q> (param: T | Q) : string {
    if (typeof param === 'string') {
        return `${param} is string type...`
    } else {
        return `${param} is number type...`
    }
}

console.log('Res > ', exampFunction <string, number> (1))
console.log('Res > ', exampFunction <string, number> ('A'))

 

위의 코드를 실행한 결과는 아래와 같다.

 

 


 

지금까지 제네릭의 기본 개념에 대해서 알아보았다. 다음 글에서는 제네릭의 기타 문법에 대해 알아보자. 오늘은 여기까지~

반응형

'재주껏 하는 Front-End > 타입스크립트 (준비중)' 카테고리의 다른 글

11. 클래스 (2)  (0) 2021.11.10
10. 클래스 (1)  (0) 2021.11.10
9. 기타 타입 (2)  (0) 2021.11.10
8. 기타 타입 (1)  (0) 2021.11.09
7. 인터페이스 (2)  (0) 2021.11.07