TypeScript

TIL | Generics

Generics(제네릭)

  • 제네릭은 재사용성이 높은 컴포넌트를 만들 때 자주 활용된다. 즉, 특정 타입에 고정되지 않고 여러 타입에서 동작할 수 있는 컴포넌트를 생성하는데 사용된다.
  • 마치 타입을 함수의 변수처럼 유동적으로 지정하여 사용하는 것을 뜻한다.
  • 제네릭은 선언 시점이 아닌, 생성 시점에 타입을 명시하여 다양한 타입의 사용을 가능케 한다.

 

제네릭을 사용하는 이유

제네릭을 사용하지 않을 경우

const getValue1 = (value: number): number => {
  return value;
};
console.log(getValue1(15)); // 15
//
const getValue2 = (value: string): string => {
  return value;
};
console.log(getValue2("value")); // value
  • 위 함수는 value라는 인자를 받아 리턴하는 함수이다.
  • 로직은 똑같지만 단지 타입이 다르다는 이유로 함수를 재생성하거나, 혹은 기존 함수의 타입을 변경해야하는 비효율적인 점이 존재한다.

 

제네릭 대신 any를 사용하면 안되는 걸까?

const getValue = function (value: any): any {
  return value;
};
  • any를 사용하면, 특정 타입에 구속되지 않고 어떤 값이든 사용할 수 있다. 그런데도 불구하고 사용하지 않는 이유는 어떤 타입이 들어가고 반환되는지 알 수가 없기 때문이다. 즉, 타입 검사 자체를 하지 않으므로 적절하지 않다.

 

제네릭을 사용할 경우

함수에서의 제네릭

// function expression
const getValue = function <T>(value: T): T {
  return value;
};
//
// arrow function
const getValue = <T>(value: T): T => {
  return value;
};
//
console.log(getValue<string>("value")); // value
console.log(getValue("value")); // value
//
console.log(getValue<number>(100)); // 100
console.log(getValue(100)); // 100
  • 제네릭을 사용할 경우, 타입을 인자처럼 사용할 수 있다. 즉 <T> 부분이 마치 타입만을 위한 매개변수처럼 작용한다고 생각하면 쉽다. 호출부를 확인해보면 타입 부분에 <number>라고 들어가게 되며, 이때 T의 자리는 모두 number가 된다.
  • 함수를 호출하는 방법은 함수명 뒤에 <> 안에 타입을 기입하는 방법과, 단순히 인자만 전달하는 방법이 있다. 보통 두 번째 방법을 많이 사용하나 복잡한 로직에서 타입 추정이 힘들시 첫번째 방법을 사용하면 된다.

 

클래스에서의 제네릭

class Person<T, N> {
  constructor(public name: T, protected gender: T, private age: N) {}
  getAge(): N {
    return this.age;
  }
}
const person = new Person("yongmin", "male", 30);
console.log(person); // Person { name: 'yongmin', gender: 'male', age: 30 }
console.log(person.getAge()); // 30
  • 제네릭 클래스를 선언할 때, 클래스 이름 오른쪽에 <T>를 붙이며, 인스턴스를 생성할 때 어떤 들어갈 지 지정하면 된다.

 

제네릭 형식 제약 조건

function text<T>(text: T): T {
  console.log(text.length); // Property 'length' does not exist on type 'T'.
  return text;
}
  • 현재 선언하는 시점에서는, 어떤 타입이 들어올지 모르는 상태이다. 즉 생성하는 시점에서 어떤 타입이 들어오는지 알 수가 없다. 때문에 length라는 속성에 접근하고자 할 경우, 타입을 알 수 없으므로 에러가 발생한다.
interface Length {
  length: number;
}
function text<T extends Length>(text: T): T {
  console.log(text.length);
  return text;
}
  • 위처럼, extends를 통해 제네릭에게 타입 힌트를 주어 해당 속성에 접근을 가능케 할 수 있다.

객체 속성 제약

const obj = {
  name: "yongmin",
  age: 20,
};
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
console.log(getValue(obj, "name")); // yongmin
  • 위처럼, 제네릭을 사용하여 객체에 있는 속성들에게만 접근하도록 지정할 수 있다.
  • K라는 타입은, T라는 타입의 키값으로부터 상속받는다.
  • 리턴 값은 T 타입의 객체에서 K 타입 key의 value이다.

Reference

'TypeScript' 카테고리의 다른 글

TIL | Index signature  (0) 2021.10.24
TIL | Interface VS Type  (0) 2021.10.24
TIL | Interface  (0) 2021.10.23
TIL | Type Inference  (0) 2021.10.23
TIL | Union, Intersection  (0) 2021.10.23