TypeScript: Generics Ep.1

Generics, Any와 다른 점


function helloString(message: string): string {
  return message
}

function helloNumber(message: number): number {
  return message
}
  • 어떤 함수가 들어오는 인자와 나가는 return 타입이 일정한 규칙을 이루면서 같은 로직을 반복하는 함수 → 더 많이 반복되는 함수들이 생긴다.
  • 해당 문제 해결을 위해서, any 사용 (모든 타입을 받고 리턴할 수 있기 때문이다.) 그러나 우리의 의도와는 다르게 다른 결과를 가져오게 된다.

Any 사용

function hello(message: any): any {
  return message
}

console.log(hello("Mark").length);
console.log(hello(19).length);
  • 문자열에서 사용하는 length를 사용하게 되면 any 형식으로 결과가 나오게 된다.
  • 심지어, length를 사용할 수 없는 number 타입도 사용할 수 있게 된다. any 타입이 되기 때문이다. 대신 런타임에 결과는 undefined이다.
  • 이때 “Mark”와 19같이 들어가는 타입을 변수 같은 것으로 활용해서 리턴되는 되는 타입과 연관을 시켜주면 좋겠다는 아이디어에서 나온 것이 Generic이다.

Generic

function helloGeneric<T>(message: T): T {
  return message
}

console.log(helloGeneric("Mark")); // "Mark"
console.log(helloGeneric("Mark").length); // number로 인식. String.length
console.log(helloGeneric(19)); // 19
console.log(helloGeneric(19).length); // number로 인식했기 때문에 length 사용 불가
console.log(helloGeneric(true)); // true
  • 괄호를 사용해서 해당 괄호 안에 T라는 단어를 사용한다. 이때, T는 Type을 의미한다.
  • 추가적인 변수(?)를 더 넣고 싶다면 <T, U, K> 이런 식으로 추가해주면 된다.
  • message가 받은 인자를 T로 한다.
  • 인자로 “Mark”를 넣으면 string으로 인식, 19를 넣으면 number로 인식한다.
  • 문자열을 리터럴로 작성 ⇒ T가 문자열이 아닌 리터럴 타입으로 인식. 즉, 문자열 리터럴 타입으로 인식되었다.
  • 19를 넣으면 number로 인식. string에서만 사용 가능한 length를 사용하려면 사용이 불가능 하다고 나온다.
  • true를 넣으면 true로 결과물이 나온다. true 타입으로 인식했다.
  • 따라서 T를 변수처럼 사용할 수 있게 된다.

Generics Basic


generic 사용방법

function helloBasic<T>(message: T): T {
  return message;
}
  • 타입 지정

    괄호안에 원하는 타입을 지정해서 사용한다.
    해당 타입이 아닌 값을 넣게 되면 에러가 발생한다.

    helloBasic<string>('number'); // string
    helloBasic<string>(19); // error
    
  • 타입 미지정

    타입을 지정하지 않기 위해서 괄호를 사용하지 않으며, 값을 넣는다.
    T에 들어간 값에 따라서 타입을 추론하여 결과를 도출한다.

    따라서 T의 값을 number가 아닌 36으로 추론한다.
    타입스크립트 입장에서 가장 좁은 범위 내에서 타입을 추론해야 하기 때문에 number가 아닌 36 그 자체로 결과를 나타낸다.
    number(더 넓은 범위) > 36(numebr 안에 속함)

    helloBasic(36); // 36
    
  • generic 변수 한개 이상 사용

    function helloBasic<T, U>(message: T, comment: U): T {
      return message;
    }
      
    helloBasic<string, number>('number', 19); // string
    // helloBasic<string>(19); // error
      
    helloBasic(36, 19); // 36
    

Generics Array & Tuple


Array

function helloArray<T>(message: T[]): T {
  return message[0];
}

helloArray(['Hello','Array']); // "Hello"
helloArray(['Hello', 5]); // string이거나 number?
  • T뒤에 배열로 받겠다는 표시로 대괄호를 붙여준다.
  • 문자열 두개를 넣었을 때, return message[0]이기 때문에 “Hello” return
  • 문자열과 숫자를 넣었을 때, string과 number가 들어갔기 때문에 결과는 “Hello”이겠지만, 추론 과정에서는 union으로 string이거나 number로 추론한다.

Tuple

function helloTuple<T, K>(message: [T, K]): T {
  return message[0];
}

helloTuple(['Hello','Array']); // string
helloTuple(['Hello',5]); // 0번째 index는 string
  • message를 튜플 형태의 타입으로 처리한다.
    따라서 0번째 요소를 return한다면 T가 return된다.
  • 문자열 두개를 넣었을 때는 string으로 return
  • 문자열과 숫자를 넣었을 때, 0번째는 string, 1번째는 number로 지정되면서 0번째에대한 return값은 string이라고 나오게된다. 만약 1번째 return값을 물어봤다면 number로 나올 것이다.

Generics Function


함수의 타입 선언 방식

함수의 타입만 정의하고 정의에 generic만 추가한다면 type alias, interface 방식으로 정의하고 사용하면 된다.
기존 함수에 generic 부분만 추가하면 되는 것이기에 간편하다.

  • type alias

    type HelloFunctionGeneric1 = <T>(message: T) => T;
      
    const helloFunction1: HelloFunctionGeneric1 = <T>(message: T) => {
      return message;
    };
    
  • interface

    interface HelloFunctionGeneric2 {
      <T>(message: T): T;
    }
      
    const helloFunction2: HelloFunctionGeneric2 = <T>(message: T) => {
      return message;
    };
    

Comments