Typescript Generic

DOHEE·2022년 10월 30일
0

<0> 내가 느낀 타입스크립트

타입스크립트를 배우면서 느낀 자바스크립트와 가장 다른 점은 타입스크립트의 이름에 있듯이 타입을 설정하는 것이다. 최대한 모든 변수에 타입을 설정하면서 바로바로 코드의 잘못된 점을 바로잡을 수 있다는 것은 큰 장점이라고 느꼈다.

하지만 모든 경우의 수를 생각하면서 변수를 설정한다는 것이 불편하고 머리가 아프다고 느껴지기도 했다. 특히, 따로 타입을 설정해야 할 정도로 까다로운 경우에는 무심코 any를 적게 됐다.

최근 읽고 있는 이펙티브 타입스크립트에서 any는 최대한 지양해야 한다고 말한 것처럼 any를 사용하는 것은 많은 문제를 야기한다. 따라서, any를 대체하는 방법 중 하나인 generic에 대해서 공부해보고자 한다.

<1> Generic을 왜 써야 할까?

generic은 데이터의 타입을 일반화한다(generalize)한다는 것을 뜻한다고 한다. 타입을 일반화한다는 말은 어떤 것을 의미할까?

타입을 일반화한다는 것은 타입에 구애받지 않고 코드를 짜게 만들어준다는 의미이다. 예를 들어, 우리가 자바스크립트에서 함수를 선언하고 그 안에 숫자를 넣든 문자를 넣든 상관하지 않듯이 사용할 수 있다는 의미이다.

하지만 자바스크립트와 동일한 방식은 아니다. generic은 선언 시점이 아니라 생성 시점에 타입을 명시하여 하나의 타입만이 아닌 다양한 타입을 사용할 수 있도록 하는 기법이다.

<2> Generic이 없다면?

그렇다면 generic이 없다면 어떻게 될까?

우리가 generic이 없이 코드를 짜는 방식은 크게 두 가지이다.

1 ) 타입 설정해주기

const newFunc = (input: number): number => {
  return input;
};

newFunc(3)
newFunc("cake") // error
newFunc([1, 2, 3]) //error

확실한 타입체크가 이뤄질 수 있겠지만 항상 number라는 타입을 받아야하므로 매개변수로 숫자를 넣을 수 있는 경우에만 사용할 수 있다.

2 ) any 사용하기

const newFunc = (input: any): any => {
  return input;
};

newFunc(3)
newFunc("cake")
newFunc([1, 2, 3])

코드의 재사용은 가능하지만 자료의 타입을 제한할 수 없을 뿐더러, 이 function을 통해 어떤 타입의 데이터가 리턴되는지 알 수 없다.

하지만 generic을 사용하면 코드를 여러번 재사용하면서도 확실한 타입체크가 가능하다.

<3> Generic 타입 변수 활용해보기

매개변수 부분의 타입을 임의의 타입, Type으로 설정해둔 뒤 생성시점(활용시점)에 Type을 특정 타입으로 지정하는 방법이다.

const newFunc = <Type>(input: Type): Type => {
  return input;
};

newFunc<number>(3)
newFunc<string>('cake')
newFunc<number[]>([1, 2, 3])
newFunc<string[]>(['a', 'b', 'c'])

위의 함수는 생성 시점에 Type을 설정하여 실질적으로 any를 사용한 것과 동일한 효과를 얻을 수 있다.

만약에 배열을 받아서 배열의 길이를 알고 싶다면 어떤 식으로 generic 타입을 지정해야 할까?

const newFunc = <Type>(input: Type): Type => {
  console.log(input.length); // error
  return input; 
};

위에서와 같은 방식은 에러가 발생한다. 어떤 타입이 올지 모르기 때문에 길이를 잴 수 있을지 확실하지 않기 때문이다. 따라서 배열이 올 것을 염두에 두고 있다면 다음과 같은 방식을 활용할 수 있다.

ver.1
const newFunc = <Type>(input: Type[]): Type[] => {
  console.log(input.length);
  return input; // error
};

ver.2
const newFunc = <Type>(input: Array(Type)): Array(Type) => {
  console.log(input.length);
  return input; // error
};

newFunc<number>([1, 2, 3]);
newFunc<string>(['a', 'b', 'c']);

<4> Generic 함수 타입 활용해보기

generic 함수 타입을 설정하는 방법은 크게 세 가지이다.

1) 함수 선언하는 것과 비슷한 방법

let newFunc: <Type>(input: Type) => Type;

2) 객체 리터럴 타입의 함수 호출 시그니처를 활용하는 방법

let newFunc: { <Type>(input: Type): Type };

3) interface를 활용하는 방법

ver 1.
interface GenericFn {
  <Type>(input: Type): Type;
}

let newFunc: GenericFn;

ver 2.
interface GenericFn <Type> {
  (input: Type): Type;
}

let newFunc: GenericFn<number>;

함수를 선언하면서 값 역시 설정해줄 수 있는데 한 부분의 타입이라도 다르면 에러가 발생한다.

const newFunc : <Type>(input: Type): Type => {
  console.log(typeof input);
  return input;
}

ver 1.
const brandNewFunc: { <Type>(input: Type): Type } = newFunc // ok

ver 2.
const brandNewFunc: { <Type>(input: Type): string } = newFunc // error

<5> Generic 클래스 타입 활용해보기

class에서도 Generic을 활용하여 재활용이 가능한 class를 만들 수 있다.

class genericClass<Type> {
  initialValue: Type,
  add: (x: Type, y: Type) => Type
}

let newClass = new genericClass<number>;
newClass.initialValue = 2;
newClass.add = (x, y) => x+y;

console.log(newClass.add(newClass.initialValue, 10)) // 12

let newClass = new genericClass<string>;
newClass.initialValue = "DO";
newClass.add = (x, y) => x+y;

console.log(newClass.add(newClass.initialValue, "HEE")) // DOHEE
profile
안녕하세요 : ) 천천히라도 꾸준히 성장하고 싶은 개발자 DOHEE 입니다! ( 프로필 이미지 출처 : https://unsplash.com/photos/_FoHMYYlatI )

0개의 댓글