Post

[TypeScript] 제네릭

[TypeScript] 제네릭

데브타스

제네릭

제네릭이란?!

  • 타입을 변수처럼 사용하는 문법
  • 다양한 타입을 받아야할 때, 타입을 미리 정하지 않고 사용할 때 정하도록 함.
  • <> 기호를 사용하며, 안에는 type의 의미인 T를 타입 변수로 사용.
  • 사용할 때, <> 기호와 타입을 명시하여 작성한다.
  • 사용 예시

    1
    2
    3
    4
    
      function getFirstElement<T>(element: T[]): T {
        return element[0];
      }
      const r1 = getFirstElement<number>([1, 2, 3]);
    
  • 그런데 사용할 때, 명시하지 않아도 정상 동작함.

    1
    2
    3
    4
    
      function getFirstElement<T>(element: T[]): T {
        return element[0];
      }
      const r1 = getFirstElement([1, 2, 3]); // 명시 X
    
  • 반환형에도 제네릭을 나타내지 않아도, 타입 추론에 의해 정상 반환됨.

    1
    2
    3
    4
    
      function getFirstElement<T>(element: T[]) { // 반환형 X
        return element[0];
      }
      const r1 = getFirstElement([1, 2, 3]); 
    

제네릭 - 함수 표현식(화살표 함수)

  • 함수 표현식에서는 다음과 같이 나타낼 수 있다.

    1
    2
    3
    
      const getFirstElement: <T>(element: T[]) => T = (element) => {
        return element[0];
      };
    

제네릭 - 타입 별칭

  • 타입 별칭에서는 다음과 같이 표현한다.
    제네릭을 타입 별칭명 옆에 제시한다.

    1
    2
    3
    4
    5
    6
    
      type MyFunc<T> = (element: T[]) => T;
      const getFirstElement: MyFunc<unknown> = (element) => {
        return element[0];
      };
      const r1 = getFirstElement([1, 2, 3]);
      const r2 = getFirstElement(["a", "b", "c"]);
    
  • 다음과 같이 사용하면, 해당 방식은 제네릭 타입별칭이 아닌,
    타입을 변수에 저장한 것에 불과한 방식이다.

    1
    2
    3
    4
    5
    6
    
      type MyFunc = <T>(element: T[]) => T;
      const getFirstElement: MyFunc = (element) => {
        return element[0];
      };
      const r1 = getFirstElement([1, 2, 3]);
      const r2 = getFirstElement(["a", "b", "c"]);
    

제네릭 - 인터페이스

  • 인터페이스에서는 다음과 같이 표현한다.
    제네릭을 인터페이스명 옆에 제시한다.

    1
    2
    3
    4
    5
    6
    
      interface MyInterface<U> {
        (element: U[]): U;
      }
      const getFirstElement: MyInterface<unknown> = (element) => {
        return element[0];
      };
    
  • 다음과 같이 사용하면, 해당 방식은 제네릭 인터페이스가 아닌,
    타입을 변수에 저장한 것에 불과한 방식이다.

    1
    2
    3
    4
    5
    6
    
      interface MyInterface {
        <T>(element: T[]): T;
      }
      const getFirstElement: MyInterface = (element) => {
        return element[0];
      };
    

제네릭의 타입 변수

  • T: Type → 일반적인 타입 변수
  • U: Another Type → 두 개 사용할 때

    1
    2
    3
    4
    5
    6
    
      function returnTuple<T, U>(a: T, b: U): [T, U] {
        return [a, b];
      }
        
      const r3 = returnTuple(1, 2);
      const r4 = returnTuple([1, 2, 3], { name: "jaegeon" });
    
  • K: Key→ 객체 타입의 키
  • V: Value→ 객체 타입의 값

    1
    2
    3
    4
    5
    
      function getValue<K, V>(obj: V, key: K): V[K] {
        return obj[key];
      }
      const name = getValue({ name: "kim", age: 20 }, "name");
      const gender = getValue({ gender: "male" }, "gender");
    

    그러나, 다음 방식은 실행 불가! → 키로 들어오는 것을 제한해야 함

  • E: Element→ 주로, 배열 요소의 타입

    1
    2
    3
    4
    5
    
      function firstElement<E>(element: E[]): E {
        return element[0];
      }
      firstElement([1, 2, 3]);
      firstElement(["a", "b"]);
    
  • R: Return Type → 반환 값의 타입(함수)

    ⇒ 2개의 제네릭: <T, U>



제네릭 심화

제네릭 타입 제약 조건

  • extends 키워드를 사용하여, 제네릭에 들어오는 타입을 제한할 수 있다.
  • 제네릭을 다음과 같이 사용시, 타입을 제한해야 한다.
    (예: length를 사용할 수 있는 타입으로 제한되어야 함)

    1
    2
    3
    4
    5
    6
    7
    
      function getLength<T>(value: T): number {
        return value.length;
      }
        
      getLength([1, 2]); // 가능
      getLength("abc");  // 가능
      getLength(10);     // 불가능
    
  • 따라서, 다음과 같이 제약 조건을 걸 수 있다.
    (예: length의 속성이 들어간 타입으로만 제한)

    1
    2
    3
    4
    5
    6
    
      function getLength<T extends { length: number }>(value: T): number {
        return value.length;
      }
      getLength([1, 2]);
      getLength("abc");
      getLength(10); // 안 됨!
    

객체의 키, 값에서의 제네릭

  • 앞서 말한 extends 키워드를 사용하면,
    값에 연결되는 키의 타입으로 제한이 가능하다.

    1
    2
    3
    4
    5
    6
    
      function getValue<K extends keyof V, V>(obj: V, key: K): V[K] {
        return obj[key];
      }
      const name = getValue({ name: "kim", age: 20 }, "name");
      const gender = getValue({ gender: "male" }, "gender");
        
    

    keyof V: V의 키를 반환

인터페이스에서의 제네릭

  • 이전에 제네릭을 인터페이스에서 사용했던 것 처럼, 사용할 때.
  • 코드의 재사용성을 높여준다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
      // 인터페이스 제네릭
      interface Car<T> {
        name: string;
        options: T;
      }
        
      const car1: Car<{ color: string }> = {
        name: "BMW",
        options: {
          color: "red",
        },
      };
      const car2: Car<{ wheels: number }> = {
        name: "G90",
        options: {
          wheels: 4,
        },
      };
    



타입스크립트 추가 개념

enum 선언 병합

  • enuminterface처럼 선언 병합이 가능

    Image

타입 가드의 검사

  • 타입 가드는 정적인 상태에서 검사하는 것이 아니다!
  • 타입스크립트의 정적 검사 원리에서 벗어나,
    타입 가드동적인 상태에서 검사하는 것이다.
  • 타입 단언은 정적 타입일 때 정의 됨.

정적 vs 동적

  • 정적 타입정적 상태: 컴파일 시점에 데이터 타입이 결정됨 → 타입스크립트
  • 동적 타입동적 상태: 코드 실행할 때, 데이터 타입이 결정됨 → 자바스크립트
This post is licensed under CC BY 4.0 by the author.