Typescript Generic (제네릭)

BitBong
8 min readOct 14, 2022

--

타입스크립트를 사용한지 거의 3개월정도가 지났다.

아직도 어렵고 생소하지만 조금씩 적응해가고 있다.

타입스크립트를 사용하면서 많이 헷갈리면서 유용한 기능 중 하나인 Generic에 대해 작성해 보고자 한다.

사실 내가 아직도 헷갈려서 기록용으로 남겨두는 개념이다.

본 게시물은 React 기준으로 작성되었습니다.

Generic

C#과 Java 같은 언어에서, 재사용 가능한 컴포넌트를 생성하는 도구상자의 주요 도구 중 하나는 제네릭입니다, 즉, 단일 타입이 아닌 다양한 타입에서 작동하는 컴포넌트를 작성할 수 있습니다. 사용자는 제네릭을 통해 여러 타입의 컴포넌트나 자신만의 타입을 사용할 수 있습니다.

Typescript 공식 문서에서는 이와 같이 제네릭을 정의하고 있다.

그렇다, 자바스크립트의 경우는 애초에 타입 관련 이슈를 대수롭지 않게 여겼던 언어라 그런지, 나에게는 조금 생소하게 다가왔다.

그래서 그런지 이해하는데 조금 어려움이 있었다. 지금도 물론 100% 이해한 상태는 아니다.

그래서 제네릭은 왜 사용해야 하나요?

예시를 한번 보자.

제네릭을 사용하지 않으면 함수에 특정 타입을 주어야 합니다.

특정 타입을 주게 되면 범용성이 떨어지게 되어 효율이 좋지 못합니다.

그럼 any를 사용하면 되긴 하는데…

any를 사용하면 모든 타입을 허용하기 때문에 나름 제네릭 형식이긴 합니다. 하지만 함수가 값을 반환할 때 어떠한 타입인지에 대한 정보는 잃게 됩니다.

예를 들어 string의 타입을 넘겨주어도 결과는 any타입으로 넘어오게 됩니다.

그럼 이제 이런 문제를 해결해 봅시다.

우리는 이제, 값이 아닌 타입 변수를 사용할 것 입니다.

identity 함수에 Type 이라는 타입 변수를 추가했습니다.

Type 유저가 준 인수의 타입을 캡처하고, 이 정보를 나중에 사용할 수 있게 합니다.

여기에서는 Type을 반환 타입으로 다시 사용합니다. 인수와 반환 타입이 같은 타입을 사용하고 있는 것을 확인할 수 있습니다.

이를 통해 타입 정보를 함수의 한쪽에서 다른 한쪽으로 운반할 수 있게끔 합니다.

any를 쓰는 것과는 다르게 인수와 반환 타입에 number를 사용한 첫 번째 identity 함수만큼 정확합니다. (반환 값의 타입이 그대로 받아와집니다.)

일단 제네릭 identity 함수를 작성하고 나면, 두 가지 방법 중 하나로 호출할 수 있습니다.

  1. 함수에 타입 인수를 포함한 모든 인수를 전달한다.
  2. 타입 인수 추론을 사용한다.

함수에 타입 인수를 포함한 모든 인수를 전달한다.

여기서 우리는 함수를 호출할 때의 인수 중 하나로써 Typestring으로 명시해 주고 인수 주변에 ()대신 <>로 감싸주었습니다.

타입 인수 추론을 사용한다.

여기서는 타입 인수 추론을 사용합니다.

우리가 전달하는 인수에 따라서 컴파일러가 Type의 값을 자동으로 정하게 됩니다.

타입 인수를 꺾쇠괄호(<>)에 담아 명시적으로 전달해 주지 않은 것을 주목하세요.

컴파일러 값인 "myString” 을 보고 그것의 타입으로 Type을 정합니다.

인수 추론은 코드를 간결하고 가독성 있게 하는 데 있어 유용하지만, 복잡한 코드에서는 컴파일러가 타입을 유추할 수 없는 경우가 발생될 수 있습니다.

제네릭을 사용하게 되면, identity와 같은 제네릭 함수를 만들 때, 컴파일러가 함수 본문에 제네릭 타입화된 매개변수를 쓰도록 합니다.

즉, 이 매개변수들은 실제로 any나 모든 타입이 될 수 있는 것처럼 취급할 수 있게 됩니다.

앞에서 본 identity함수를 다시 봐봅시다.

함수 호출 시마다 인수 arg의 길이를 로그에 찍으려면 어떻게 해야 할까요?

이러한 방법을 생각했을 겁니다.

하지만 이렇게 하면 , 컴파일러는 arg의 멤버 .length을 사용하고 있다는 오류를 낼 것입니다.

어떤 곳에서도 arg가 이 멤버가 있다는 것이 명시 되어 있지 않았습니다. 이전에 이러한 변수 타입은 any나 모든 타입을 의미한다고 했던 것을 상기시켜보세요.

따라서 이 함수를 사용하고 있는 사용자는 .length멤버가 없는 number를 대신 전달할 수도 있습니다.

loggingIdentity의 타입을 “제너릭 함수 loggingIdentity는 타입 매개변수 TypeType 배열인 인수 arg를 취하고 Type 배열을 반환한다.”라고 읽을 수 있습니다.

만약 우리가 number 배열을 넘기면 Typenumber에 바인딩 되므로 함수는 number 배열을 얻게 됩니다.

전체 타입 변수를 쓰는 것보다 하나의 타입으로써 제네릭 타입 변수 Type를 사용하는 것은 굉장한 유연함을 제공합니다.

제네릭 타입

네, 이제야 제대로 제네릭 타입에 대해 알아보도록 하겠습니다.

위에 내용들은 제네릭에 대한 내용이었거든요.

우리는 앞서 만든 identity 함수를 이용하여 제네릭 인터페이스를 만들어 보겠습니다.

제네릭 함수의 타입은 함수 선언과 유사하게 타입 매개변수가 먼저 나열되는, 비제네릭 함수의 타입과 비슷합니다.

또한 타입 변수의 수와 타입 변수가 사용되는 방식에 따라 타입의 제네릭 타입 매개변수에 다른 이름을 사용할 수도 있습니다.

제네릭 타입을 객체 리터럴 타입의 함수 호출 시그니처로 작성할 수도 있습니다.

위 두 예제로 첫 번째 제네릭 인터페이스를 작성할 수 있습니다.

비슷한 예제로, 제네릭 매개변수를 전체 인터페이스의 매개변수로 옮기고 싶을 수도 있습니다.

이를 통해 제네릭 타입을 확인할 수 있습니다.

이렇게 하면 인터페이스의 다른 모든 멤버가 타입 매개변수를 확인할 수 있습니다.

그래서 React에서는 어떻게 사용해야하나요 ?

네, 아직 React에서 어떻게 사용하는지는 모릅니다.

하지만 이제 알게 될 겁니다.

첫 번째로는 Hook 에서 제네릭을 사용하는 방법을 알아봅시다.

React Hook에서 제네릭 사용하기

Hook은 React가 조금 다르게 취급하는 일반적인 Javascript 함수입니다.

Hook과 함께 제네릭 유형을 사용하는 것은 일반 Javascript 함수와 함께 사용하는 방법과 동일합니다.

위의 예시에서는 Typescript가 인수 값에서 유추할 수 있으므로 명시적으로 제네릭을 생략할 수 있습니다.

React에서 모든 Hook을 입력하는 방법에 대해 자세히 알고 싶다면 각 Hook을 입력하는 방법 링크를 참조하세요.

Component props 제네릭 사용하기

아래와 같은 Component를 만든다고 가정해 보겠습니다.

event 에서 객체 유형에 어떤 이벤트가 일어나고 있는지 확실하지 않은 경우 React에서 이벤트와 함께 Typescript를 사용하는 방법을 handleChange 설명하는 문서를 참고 하세요 !

value 값에 대하여 문자열이나 숫자 중 하나를 받아올 수 있지만 동시에 둘 다 받아 올 수는 없다고 가정해 보겠습니다.

해당 Component에서 어떻게 사용할 수 있을까요?

이렇게 사용을 할 수도 있겠지만, Typescript는 이러한 형식을 허용하지 않습니다.

options 에 있는 배열의 값 유형이 숫자인 옵션과 문자열 유형의 값이 다른 옵션이 있을 수 있기 때문입니다.

숫자 or 정수 중 하나를 원하는 값을 사용하기 위해서는 제네릭을 사용하는 것입니다.

제네릭에 익숙하지 않으면 어색해 보일 수 있는 코드입니다.

(물론 저도 아직도 익숙하진 않습니다.)

해당 코드를 보면 OptionValue를 왜 이리 많이 사용하는지 의문을 가질 수 있습니다.

저렇게 정의하지 않고 Type 을 그냥 주입한다고 생각해 보세요.

Select Component에 삽입되는 PropsType 이 어떠한 것인지 제대로 파악 할 수가 없습니다.

그렇기 때문에 정확한 Type 을 지정해야 하기 때문에, 위와 같이 사용하게 됩니다.

제네릭과 무관한 내용이지만 실제 IDE에서 해당 코드를 작성하게 되면 handleChange 함수 내부에서 Typescript 오류가 발생할 수 있습니다.

그 이유는 event.target.value 는 숫자인 경우에도 문자열로 반환되기 때문입니다.

해당 문제를 처리하기 가장 좋은 방법은 아래와 같이 선택한 요소의 인덱스를 사용하는 것입니다.

마무리

사실 아직도 제네릭에 대한 개념이 완벽하게 잡히진 않았다.

그래도 뭐 어쩌겠나… 계속 스터디하는 수밖에…

--

--