Generics(제너릭)

Hyun·2022년 6월 29일
0

typescript

목록 보기
1/3

Generics 는 타입스크립트에서 함수, 클래스, interface, type aliase 를 사용하게 될 때 여러 종류의 타입에 대하여 호환성을 제공해야 하는 상황에서 편리하게 쓰이는 문법이다.

함수에서의 generics

function merge(a: any, b: any): any {
    return {
      ...a,
      ...b
    };
  }
  
  const merged = merge({ foo: 1 }, { bar: 1 });

특정한 타입을 지정해주지 않은 상태에서는 기본적으로 any 타입을 사용하게 되는데 (any 타입을 지정해주지 않으면 오류가 발생한다), any 는 해당 함수에 주어지는 인자가 object 인지 string 인지 number 인지 아무것도 모르는 상태, 즉 타입을 유추할 수 없는 상태를 말한다.

이런 상황에서 generics 를 사용할 수 있다.

function merge<A, B>(a: A, b: B): A & B {
    return {
      ...a,
      ...b
    };
  }
  
  const merged = merge({ foo: 1 }, { bar: 1 });

다음과 같이 함수의 이름 오른쪽에 <A, B> 로 각각의 인자에 대응되는 타입 매개변수를 지정해주면 타입스크립트가 스스로 타입을 유추한다. 따라서 우리는 merge 함수의 인자에 들어오는 값들이 { foo: number }, {bar: number} 형태의 타입을 갖는다는 것을 확인할 수 있다.

=> generics 를 이용하여 값들의 타입을 상황에 맞게 자동으로 유추할 수 있다.

이런 생각이 들 수 있다. "어차피 인자로 {foo:1}, {bar:1} 을 집어넣을거면 아래 사진처럼 그냥 인자의 타입을 any 가 아니라 명확하게 object 타입으로 지정해주면 되는 것 아닌가?"

위와 같은 예제에서는 굳이 타입 유추를 위한 타입 매개변수<> 를 지정해줄 필요없이 그냥 바로 명확하게 object 타입을 명시해줘도 상관없다고 생각한다.

하지만 만약 내가 어떤 함수에 인자로 object 만 넣고 싶은게 아니라 number 타입도 넣고 싶고, string 타입도 넣으면서 이 함수의 기능을 재사용하고 싶을 때, generics 를 사용하면 편리하다.

예를 들어 어떤 배열의 길이를 출력하는 함수를 타입스크립트로 작성한다고 했을 때, 숫자 배열의 길이만을 출력하는 함수는 아래와 같을 것이다.

 function getSize(arr: number[]): number{
     return arr.length;
 }

만약 문자열 배열, 불린 배열 등등의 길이를 출력하고 싶으면 일일이 추가해주어야 한다. 함수의 재사용과는 거리가 멀다.

function getSize(arr: number[] | string[] | boolean[]): number{
    return arr.length;
}

이때 여러 가지 타입의 배열의 길이를 손쉽게, 함수를 재사용하여 출력하기 위해 generics 를 사용할 수 있다.

function getSize<T>(arr: T[]) : number {
    return arr.length;
}

타입 매개변수를 지정하고, 함수를 사용할때 타입을 지정해주면 된다. (함수의 재사용성 ↑)

물론 위와 같이 타입 매개변수만을 지정해주고, 알아서 인자의 타입을 자동으로 유추시킬 수 있다. 이를 통해 적어도 타입이 any 가 아닌 인자로 받는 값의 타입이란걸 타입스크립트가 알고 있다는 말이다.

하지만 인자의 타입을 내가 정확하게 지정해줌으로서 타입을 자동으로 "유추"하는게 아니라 내가 타입을 정하고, 그에 맞는 타입의 값을 인자로 주어 함수를 사용할 수 있다. 내가 타입을 정하고 그에 맞지 않는 타입의 값을 인자로 넣었을 경우 에러가 발생하는 것을 볼 수 있다.

<결론>

타입 매개변수를 사용하지 않았을 때

  • 타입을 무조건 any 로 받아들이거나, 내가 정한 타입만 사용가능
  • 재사용할 수 없고, 타입이 any 거나, 내가 정한 타입에서 다른 타입의 값을 받을려면 아래와 같이 일일이 추가해야 한다.
function getSize(arr: number[] | string[] | boolean[]): number{
    return arr.length;
}

타입 매개변수만을 사용하고, 타입을 지정하지 않은 경우

  • 타입스크립트가 인자의 타입을 스스로 유추가능(내가 정하는 것 X)
  • 재사용 가능(타입 지정X, 유추O)

타입 매개변수를 사용하고 동시에 타입을 지정한 경우

  • 해당 함수의 매개변수의 타입을 내가 정하고, 그에 맞는 타입의 값을 인자로 넘겨줬는지 확인 가능하다.
  • 재사용 가능(타입 지정O)

인터페이스에서의 generics

interface 의 경우도 내가 타입을 사용할때 겹치는 부분이 있고 다른 부분은 유동적으로 원하는 타입을 사용하고 싶을 때 generics 를 사용하면 편리하다.

아래 예시처럼 몇가지 부분의 타입은 고정시키고, 바꾸고 싶은 부분만 타입 매개변수를 사용하여 원하는 타입을 지정하여 사용할 수 있다.

interface Mobile<T>{
    name: string;
    price: number;
    option: T;
}

const m1: Mobile<{color: string, coupon: boolean}> = {
    name: "s21",
    price : 1000,
    option: {
        color: "red",
        coupon: false,
    }
}

타입 매개변수를 이용하여 함수의 인자의 타입을 정할 때, 우선, 비교의 대상인 자바스크립트는 아래와 같다. 타입을 any 로 지정한 경우도 자바스크립트와 똑같이 동작할 것이다.

이런 경우, 코딩을 다~ 하고 컴파일한 이후에 잘못됐다는 것을 알 수 있기 때문에, 애초에 타입스크립트에서 타입을 맞게 지정했는지 검사하는 과정을 통해 컴파일 전 잘못된 부분을 알 수 있다.

function showName(data) {// typescript => (data: any)
    return data.name; 
  // 모든 매개변수에 name 이 있다고 확신할 수 없음 
  // 컴파일 이후 data 에 name 이 없는 경우 undefined return 됌 
}

따라서 컴파일하기 전에 올바른 타입을 사용했는지 확인하기 위해 타입 매개변수를 사용한다. 타입 매개변수만 혼자 사용한 경우, 해당 타입이 name 속성이 있는지 없는지 모르기 때문에 에러가 발생한다.

에러가 발생하는 것을 막고, 올바른 타입을 사용했는지 확인하기 위해 extends 를 사용한다. showName 에 사용하는 타입은 무조건 extends 이후의 타입을 가지고 있어야 하는 것이다. 해당 타입을 가지고 있지 않은 객체의 경우 에러가 발생하여 함수를 사용할 수 없는 것을 볼 수 있다.

[출처 및 참고자료]
유튜브 코딩앙마님 TypeScript #7 제네릭 Generics
velopert님 01. 타입스크립트 연습

profile
better than yesterday

0개의 댓글