(TS) Generic

Mirrer·2022년 12월 26일
0

TypeScript

목록 보기
9/14
post-thumbnail

Generic

Class 또는 함수에서 사용할 Type을 사용할 때 결정하는 프로그래밍 기법

GenericC#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이다.

특히, 한가지 타입보다 여러 타입에서 동작하는 컴포넌트를 생성할 때 주로 사용된다.

Generic이란, 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다.


Example

아래 함수는 text라는 파라미터를 전달받아 text를 반환한다.

이 때 파라미터는 타입이 지정되어 있지 않아 어떤 타입을 전달받아도 그 값을 그대로 반환한다.

function getText(text) {
  return text;
}

getText('hi'); // 'hi'
getText(10); // 10
getText(true); // true

해당 함수에 Generic 기본 문법을 적용하면 다음과 같이 함수를 호출할 때 함수 안에서 사용할 타입을 전달할 수 있다.

function getText<T>(text: T): T {
  return text;
}

getText<string>('hi'); // T = string
getText<number>(10); // T = number
getText<boolean>(true); // T = boolean

Why Use?

아래 코드는 인자를 하나 전달 받아 반환해주는 함수이다.

function logText(text: string): string {
  return text;
}

여기서 이 함수의 인자와 반환 값은 모두 string으로 지정되어 있지만 만약 여러 가지 타입을 허용하고 싶다면 아래와 같이 any를 사용할 수 있다.

function logText(text: any): any {
  return text;
}

위의 코드를 실행하면 어떠한 에러도 발생하지 않고 정상적으로 실행된다.

다만, any 타입은 타입 검사를 하지 않기 때문에 함수의 인자로 어떤 타입이 전달됬고, 어떤 값이 반환되는지 알 수 없다.

이러한 문제를 해결하기 위해서 Generic을 사용한다.


function logText<T>(text: T): T {
  return text;
}  

위와 같은 문제를 해결하기 위해 함수 이름 뒤에 <T> Generic을 추가했다.

그리고 함수의 인자와 반환 값에 모두 T 라는 Generic 타입을 추가한다.

이렇게 되면 함수를 호출할 때 넘긴 타입에 대해 Typescript가 추정할 수 있게 된다.

따라서, 함수의 입력 값에 대한 타입출력 값에 대한 타입동일한지 검증을 할 수 있게 된다.

그리고 이렇게 선언된 함수는 아래와 같이 2가지 방법으로 호출할 수 있다.

// #1
const text = logText<string>("Hello Generic");

// #2
const text = logText("Hello Generic");

보통 코드 간결하고, 가독성이 좋아 두 번째 방법을 주로 사용한다.
만약 복잡한 코드에서 두 번째 방법으로 타입 추정이 되지 않는다면 첫 번째 방법을 사용한다.


Generic Type Variable

Generic을 사용하면 Compiler를 통해 타입을 전달하라는 경고를 볼 수 있다.

위 코드의 logText 함수 인자 length를 확인하고 싶다면 아마 다음과 같이 코드를 작성할 것이다.

function logText<T>(text: T): T {
  console.log(text.length); // Error: T doesn't have .length
  return text;
}

하지만 해당 코드는 text 파라미터에 .length가 존재하는 단서가 없기 때문에 Compiler에서 에러를 발생시킨다.

그래서 이런 경우에는 아래와 같이 Generic에 타입을 지정할 수 있다.

function logText<T>(text: T[]): T[] {
  console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용
  return text;
}

이런 방식으로 Generic을 사용하면 이전보다 유연한 방식으로 함수의 타입을 정의할 수 있다.

만약 좀 더 명시적으로 Generic 타입을 선언하고 싶다면 다음과 같이 작성할 수 있다.

function logText<T>(text: Array<T>): Array<T> {
  console.log(text.length);
  return text;
}

Generic Type

아래의 두 코드는 같은 의미를 가진 코드이다.

function logText<T>(text: T): T {
  return text;
}
  
// #1
let str: <T>(text: T) => T = logText;
  
// #2
let str: {<T>(text: T): T} = logText;

위와 같은 변형 방식으로 Generic Interface 코드를 다음과 같이 작성할 수 있다.

interface GenericLogTextFn {
  <T>(text: T): T;
}
    
function logText<T>(text: T): T {
  return text;
}
    
let myString: GenericLogTextFn = logText; // Okay

위 코드에서 Interface인자 타입을 강조하고 싶다면 아래와 같이 변경할 수 있다.

interface GenericLogTextFn<T> {
  (text: T): T;
}
    
function logText<T>(text: T): T {
  return text;
}
    
let myString: GenericLogTextFn<string> = logText;

위의 방식을 이용하면 Class 또한 생성할 수 있다.
단, EnumNamespaceGeneric으로 생성할 수 없다.


Generic Class

Generic Class는 앞서 설명한 Generic Interface와 비슷하며, 선언시 Class 이름에 <T>를 추가한다.

그리고 인스턴스를 생성할 때 타입에 지정될 값을 입력하면 된다.

class GenericMath<T> {
  pi: T;
  sum: (x: T, y: T) => T;
}

let math = new GenericMath<number>();

Constraints

Generic 함수에도 타입 힌트를 줄 수 있다.

function logText<T>(text: T): T {
  console.log(text.length); // Error: T doesn't have .length
  return text;
}

위 코드의 logText 함수 인자의 타입에 선언한 T는 아직 어떤 타입인지 구체적으로 정의하지 않아 length에서 오류가 발생한다.

이럴 때 해당 타입을 구체적으로 정의하지 않고 length 속성을 허용하려면 아래와 같이 작성한다.

interface LengthWise {
  length: number;
}

function logText<T extends LengthWise>(text: T): T {
  console.log(text.length);
  return text;
}
    
logText(10); // Error, 숫자 타입에는 `length`가 존재하지 않으므로 오류 발생
logText({ length: 0, value: 'hi' }); // `text.length` 코드는 객체의 속성 접근과 같이 동작하므로 오류 없음

객체 속성 제약

두 객체를 비교할 때도 다음과 같이 Generic 제약 조건을 사용할 수 있다.

function getProperty<T, O extends keyof T>(obj: T, key: O) {
  return obj[key];  
}
let obj = { a: 1, b: 2, c: 3 };

getProperty(obj, "a"); // okay
getProperty(obj, "z"); // error: "z"는 "a", "b", "c" 속성에 해당하지 않습니다.

위 코드는 Generic 선언시 <O extends keyof T> 부분에서 첫 번째 인자로 받는 객체에 없는 속성들은 접근하지 못하게 제한했다.


참고 자료

TypeScript: JavaScript With Syntax For Types.
React TypeScript Tutorial for Beginners - Codevolution
타입스크립트 입문 - 기초부터 실전까지 - 장기효

profile
memories Of A front-end web developer

0개의 댓글