본문 바로가기

카테고리 없음

TypeScript - Generics

 

Generic의 사전적인 의미로는 '포괄적인, 일반적인'이라는 뜻을 가지고 있습니다.

이 사전적 의미로 유추해 보면, Generic 타입은 여러 타입을 포괄적으로 다룬다는 뜻으로 해석할 수도 있을 것입니다.

 

Generic 타입은 실제 타입이 지정되지 않았다가, 사용될 때 실제 타입이 결정되는 특수한 타입입니다.

 

function tryGeneric<T>(message: T): T {
  return message;
}

console.log( tryGeneric('Jeong') );

 

  • 위 코드에서 tryGeneric 함수는 Generic 타입인 message를 인자로 받고 있고, Generic 타입의 반환 결과를 가지고 있습니다.
  • 이때, tryGeneric('Jeong')을 사용하게 되면, string 타입의 인자가 전달되고, tryGeneric 함수의 T는 string 타입으로 결정됩니다.

 

그렇다면 Generic과 비슷한 타입인 any와는 어떤 차이가 있을까요?

 

function tryGeneric<T>(message: T): T {
  message = 100; // 오류: 'T'은(는) 'number'과(와) 관련되지 않은 임의의 형식으로 인스턴스화할 수 있습니다.
  return message;
}

console.log( tryGeneric('hello, world') );


function tryAny(message: any): any {
  message = 100;
  return message;
}

console.log( tryAny('hello, world') );

 

  • Generic 타입의 경우, 전달받은 T라는 타입이 하나로 고정되기 때문에, 중간에 타입이 변하면 오류가 발생합니다.
  • any 타입의 경우에도, 인자로 전달받을 땐 동일하게 어느 타입이든 받을 수 있지만, 리턴 타입도 any이기 때문에 중간에 타입이 변경되어도 문제가 없습니다.
  • 따라서, 타입 검사를 좀 더 명확히 할 수 있다는 차이점이 있습니다.

 

이제 Generic 타입을 사용하는 방법에 대해 알아보겠습니다.

 

1. Generic 기본 사용법

  • 여러 개의 Generic 타입을 동시에 사용할 수 있습니다.
  • Generic 타입은 사용 방법에 따라, 명시적 또는 묵시적으로 정의될 수 있습니다.
function tryGeneric<T, U>(message: T, comment: U) {
  return message;
}

tryGeneric<string, number>('hello', 100); // 명시적 타입 정의
tryGeneric('hello', 100); // 묵시적 타입 정의

 

2. Generic Array & Tuple

  • Generic 타입을 ArrayTuple로 사용할 수 있습니다.
function genericArray<T>(message: T[]): T {
  return message[0];
}

genericArray(['Jeong', 'Hyeonjun']);
genericArray(['Jeong', 32]); // 리턴 타입이 정확히 추론되지 않는다.


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

genericTuple(['Jeong', 'Hyeonjun']);
genericTuple(['Jeong', 32]); // 리턴 타입이 정확히 추론되기 때문에, 메서드 체이닝을 사용할 수 있다.

 

3. Generic Function

  • Type Alias 또는 Interface에 Generic 타입을 적용해 함수의 규격을 만들 수 있습니다.
// Type Alias
type genericTypeAlias = <T>(message: T) => T;

const fn1: genericTypeAlias = <T>(message: T): T => {
  return message;
}


// Interface
interface genericInterface {
  <T>(message: T): T;
}

const fn2: genericInterface = <T>(message: T): T => {
  return message;
}

 

4. Generic Class

  • 클래스에도 Generic 타입을 적용할 수 있습니다.
class Person<T> {
  constructor(private name: T) {
    this.name = name;
  }

  setName(name: T) {
    this.name = name;
  }
}

const p1 = new Person(10);
const p2 = new Person('hello');

p1.setName('Jeong'); // 오류: 생성자에서 타입이 number로 결정되었기 때문에 문자열을 인자로 전달할 수 없음

 

5. Generic extends

  • extends 키워드를 사용해서 Generic 타입의 범위를 제한할 수 있습니다.
class Person<T extends string | number> {
  constructor(private name: T) {
    this.name = name;
  }
}

new Person('hello');
new Person(100);
new Person(true); // 오류: string 또는 number 타입을 전달해야 합니다.

 

6. keyof & type look-up system

  • keyof 키워드는 타입 값에 존재하는 모든 프로퍼티의 키값을 union 형태로 리턴받습니다.
    • interface Person {
        name: string;
        age: number;
      }
      
      type person = keyof Person; // person 타입은 'name' | 'age'로 정의됨
      const a: person = 'name';
interface Person {
  name: string;
  age: number;
}

const person: Person = {
  name: 'Jeong',
  age: 32
};

function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

function setProp<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;
}