[TS] 제너릭(Generic)

mokyoungg·2021년 4월 12일
5

타입스크립트 공부

목록 보기
8/10

제너릭이란 무엇인가?

제너릭 타입은 타입에 유연성을 제공하여 컴포넌트 등에서 재사용을 가능하게 해주는 타입이다.
타입의 유연성이란 :string, :number 등과 같이 고정된 타입이 아닌 사용에 따라 여러 타입을 사용하게 해준다는 것이다.

이는 any 타입과 매우 흡사하지만 차이점이 있다.

제너릭은 타입 정보가 동적으로 결정되는 타입이다.
출처: 실전 리액트 프로그래밍

제너릭 타입은 다양한 타입을 받을 수 있다는 유연성이란 점에서 any 타입과 흡사하지만 타입의 정보가 동적으로 결정된다는 차이가 있다.

any 타입과 제너릭

any 타입 사용 코드

다음 any type을 사용한 코드이다.

  • 함수 identity는 any 타입으로 인자를 받아 그 인자를 반환하는 함수이다.
  • idetity(1)의 결과 1을 example1 이라는 변수에 할당하였을 때
  • example1 값의 타입은 any이다.
  • 따라서 함수 호출의 결과의 실제 타입은 숫자(1)지만 문자열 메서드인 split()를 사용할 수 있는 오류가 발생한다.

  • 위의 코드를 수정하려면 identity의 반환값을 number로 설정하여 문자열 메서드를 사용하지 못하게 해야 한다.

generic 사용 코드

다음은 generic을 사용한 코드이다.

  • any 타입이 아닌 제너릭('< T >')을 사용하였다.
  • identity2 함수의 반환 타입도 T로 설정하였다.
  • 이 결과 숫자(1)을 넣었을 때 그 인자의 타입이 T로 설정된다.
  • 따라서 문자열 메서드인 split() 사용에 오류를 감지한다.

  • 제너릭은 any 타입과 마찬가지로 다양한 유형을 받을 수 있다.
  • idetitiy2()의 인자로 이번엔 문자열('gExample')을 전달하였다.(유연성)
  • 전달한 문자열에 따라 제너릭 T의 유형이 문자열로 설정된다.
  • 따라서 문자열 메서드인 split() 사용에 문제가 없다.

제너릭 타입 정보가 동적으로 결정된다는 것은 이런 뜻이다.
위와 같이 함수가 호출될 때(동적) 타입이 유연하게 결정된다.

any를 쓰는 것은 함수의 arg가 어떤 타입이든 받을 수 있다는 점에서 제네릭이지만, 실제로 함수가 반환할 때 어떤 타입인지에 대한 정보는 잃게 됩니다. 대신에 우리는 무엇이 반환되는지 표시하기 위해 인수의 타입을 캡처할 방법이 필요합니다.

인수와 반환 타입이 같은 타입을 사용하여, 타입 정보를 함수의 한쪽에서 다른 한쪽으로 운반할 수 있게끔 합니다.

출처 : https://typescript-kr.github.io/pages/generics.html

제너릭과 함수

함수에서 제너릭 정의

제너릭은 <> 기호를 이용해서 정의하며, 이름은 자유롭게 지정할 수 있다.
function 함수이름 <제너릭이름>(인수: 제너릭 이름) : 제너릭 이름 {}

제너릭으로 재사용 가능한 함수 만들기

코드 출처 : 실전리액트 프로그래밍

  • 위의 코드는 숫자형 배열과 문자형 배열을 만드는 함수이다.
  • 다른 타입의 배열을 만들기 위해 두 개의 함수의 코드를 작성했지만 중복된 코드가 많이 보인다.

코드 출처 : 실전리액트 프로그래밍

  • 함수를 선언할때의 T는 함수 호출시 입력이 되기 때문에 아직 어떤 타입인지 결정되지 않았다.
  • 호출될때의 T를 함수 내부에서도 사용한다.(arr: T[] = [])
  • 호출시 타입 T의 정보를 전달한다.(arr1, arr2)
  • 작성한 코드에서, 함수의 첫번째 매개변수를 알면 타입T도 알 수 있기 떄문에 호출시 타입 T의 정보를 명시적으로 전달하지 않아도 된다.(arr3, arr4)

제너릭 사용 함수 2

  • 제너릭은 동적으로 타입이 결정된다.
  • 위의 코드에서 작성한 함수는 호출되지 않은 선언만 된 상태라 제너릭(T)의 타입을 알 수 없으며 해당 타입에서 length 속성을 읽을 수 없다는 오류가 발생한다.
  • 읽을 수 없기때문에 element.length의 값이 무엇인지 알 수 없다. 따라서 숫자와의 비교(===, >, <)를 할 수 없다.

  • 이럴 경우 interface를 설정하여 제너릭이 interface를 가져오면 된다.
  • length의 값이 숫자라는 것을 알게 되어 오류가 발생하지 않는다.

제너릭 제약 조건

제너릭의 제약 조건은 제너릭(T)의 타입을 제한한다.
이러한 제약 조건은 extends 키워드로 지정한다.

제너릭 제약 조건 만들기

fuction 함수 이름< 제너릭 이름 extends 제한 조건 > (인수 : 제너릭 이름) : 제너릭이름 {}

  • 위의 코드는 제너릭 T에 제약 조건을 걸었다.
  • 제약 조건은 제너릭 T는 숫자 또는 문자여야 한다는 것이다.
  • 따라서 객체 형태가 전달되는 arr5, 불리언 형태가 전달되는 arr6에서 오류가 발생한다.

keyof 제약 조건

제너릭을 사용하여 객체의 key 값에 조건을 걸 수 있다.
예를 들어 객체 안에 있는 key의 값만을 받고 싶을 때 다음과 같은 코드를 작성할 수 있다.
(keyof 의 제약)

  • 제너릭 T는 객체 타입으로 제한하였다.
  • 제너릭 U는 T의 키로만 제한하였다.(U extends keyof T)

  • 위와같이, keyof 로 제네릭T의 키로만 제한하였기 때문에 extractAndCover의 두번째 인자로'name'이 들어가면 오류가 발생한다.
  • 첫번째 인자로 들어가는 객체 {}에는 'name'이라는 key가 없기 때문이다.

  • 첫벗째 인자에 들어가는 객체에 key로 name을 주었기 때문에 두번째 인자 'name'을 전달하여도 오류가 발생하지 않는다.

이를 통해 존재하지 않는 속성에 접근하는 것을 막을 수 있다.

제너릭과 클래스

클래스에서 제너릭 정의

Class 클래스이름 <제너릭 이름> {}

  • DataStorage 클래스를 제너릭 타입(T)와 함께 사용하였다.
  • 해당 클래스는 인스턴스를 만들때 유연하게 타입을 받는다.
  • newDataStorage()에 제너릭 타입을 설정하여 문자형 또는 숫자형으로 인스탄스의 타입을 설정하였다.
  • textStorage는 문자형을 받기 때문에 addItem(숫자)에서 오류가 발생하며 같은 이유로 numberStorage.addItem(문자형)에도 오류가 발생한다.



참고

타입스크립트 핸드북
https://typescript-kr.github.io/
실전리액트프로그래밍(개정판) - 이재승지음
http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788966262670
Understanding TypeScript - 2021 Edition(Udemy강의)
https://www.udemy.com/course/understanding-typescript/

profile
생경하다.

0개의 댓글