본문 바로가기
스터디

[Typescript] 제네릭 (Generics)

by rious275 2023. 6. 12.

제네릭의 Hello World (Hello World of Generics)

아래의 identity 함수는 인수로 무엇이 오던 그대로 반환한다. 제네릭이 없다면 아래처럼 특정 타입을 주어야 한다.

function identity(arg: number): number {
  return arg;
}

혹은 any 타입을 사용할 수 있지만, 우리는 무엇이 반환되는지 알아야하며 인수의 타입을 캡처할 방법이 필요하다. 제네릭을 사용하면 아래처럼 표시할 수 있다.

// 선언식
function identity<T>(arg: T): T {
  return arg;
}

// 표현식
const identity2: <T>(arg: T) => T = arg => arg;

함수에 T라는 타입 변수를 추가했고, T는 사용자가 준 인수의 타입을 캡처하고 나중에 사용할 수 있게 한다.

제네릭 함수를 작성하고 나면 두 가지 방법 중 하나로 호출할 수 있는데, 첫 번째 방법은 함수에 타입 인수를 포함한 모든 인수를 전달하는 방법이다.

let output = identity<string>('myString');

두 번째 방법은 일반적인 방법으로, 타입 인수 추론을 사용한다. 즉, 사용자가 전달하는 인수에 따라 컴파일러가 자동으로 추론하게 하는 것이다.

let output = identity('myString');

제네릭 타입 변수 작업 (Working with Generic Type Variables)

제네릭 사용 시, 컴파일러는 함수 본문에 제네릭 타입화된 매개변수를 쓰도록 강요한다. 즉, 이 매개변수들은 실제로 any처럼 모든 타입이 될 수 있는 것처럼 취급한다.

위의 identity 함수 호출시마다 arg의 길이를 로그 찍으려면 아래처럼 할 것이다.

function logging<T>(arg: T) {
  console.log(arg.length); // X 타입 변수가 number일 경우 length 프로퍼티가 없을 수 있다.
  return arg;
}

위의 경우, 타입 변수 자체를 length 프로퍼티가 있는 배열을 받도록 수정할 수 있다.

function logging<T>(arg: T[]) {
  console.log(arg.length); // O
  return arg;
}

제네릭 타입 (Generic Types)

제네릭 함수 타입은 일반 함수 선언과 유사하게 타입 매개변수가 먼저 나열된다.

제네릭 타입 매개변수는 다른 이름으로도 사용할 수 있으며, 객체 리터럴 타입의 함수 호출 시그니처로 작성할 수도 있다.

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: <U>(arg: U) => U = identity; // 다른 매개변수 네이밍
let myIdentity: { <T>(arg: T): T } = identity; // 객체 리터럴 타입의 함수 호출 시그니처

위에서 작성한 객체 리터럴을 가져와 첫 번째 제네릭 인터페이스를 작성할 수 있다.

interface GenericIdentityFn {
  <T>(arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn = identity;

제네릭 매개변수를 인터페이스 매개변수로 옮기면, 인터페이스의 다른 모든 멤버가 타입 매개변수를 확인할 수 있다.

interface GenericIdentityFn {
  (arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

제네릭 제약조건 (Generic Constraints)

특정 타입들로만 동작하는 제네릭 함수를 만들고 싶을 떄가 있는데, 이를 작성하려면 T가 무엇이 될 수 있는지 제햑 조건을 나열해야 한다.

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

제약조건을 명시하는 인터페이스를 만들고, 제약사항을 extends 키워드로 명시했다. 이제, 제한되어 있기 때문에 length 프로퍼티가 있어야만 동작한다.

loggingIdentity(3); // X number 타입은 length 프로퍼티가 없음

제네릭 제약조건에서 타입 매개변수 사용 (Using Type Parameters in Generic Constraints)

이름이 있는 객체에서 프로퍼티를 가져오고 싶은 경우, 실수로 obj에 존재하지 않는 프로퍼티를 가져오지 않도록 하기 위해 두 가지 타입에 제약조건을 둘 수 있다.

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

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, 'a'); // O
getProperty(x, 'm'); // X

'스터디' 카테고리의 다른 글

[Typescript] 모듈 (module)  (0) 2023.07.07
[Typescript] 유틸리티 타입  (1) 2023.06.19
[Typescript] 열거형 (Enums)  (0) 2023.06.11
[Typescript] 유니언 타입과 교차 타입  (0) 2023.06.11
[Typescript] 리터럴 타입  (0) 2023.06.10