본문 바로가기
Frontend/TypeScript

[TypeScript] TypeScript 인터페이스/클래스/제너릭 타입 이해 - 4

by 신림쥐 2024. 4. 14.
728x90

 

     


    인터페이스란?

    - 자바스크립트에서는 사용하지 않는 타입스크립트 문법

    - 변수 나 함수 , 그리고 클래스 가 만족해야하는 최소 규격을 지정할 수 있게 해주는 도구

     

    인터페이스 기본 문법

    - 타입 별칭과 동일한 타입을 지정하는 방법 중 하나로 c, java 의 인터페이스 처럼 작성하는 방법

    - 선택적 프로퍼티,  읽기전용 프로퍼티가 가능하다.

    - 매개변수, 반환값을 포함한 메서드 오버로딩 타입 지정도 가능하다.

    - 대신, 별칭 처럼 | 연산자를 사용하여 인터페이스를 여러개 지정할 수는 없다.

    // 인터페이스
    interface Person {
      readonly name: string;
      age?: number;
      sayHi(): void;
      sayHi(a: number): void;
      sayHi(a: number, b: number): void;
    }
    
    const person: Person = {
      name: "kim",
      sayHi: function () {
        console.log("sayHi");
      },
    };
    // person.name = "홍길동"; // error - 할당 값 수정 불가
    person.sayHi();
    
    // interface Person { // 인터페이스는 | 연산자로 타입 지정 안됨
    //   name: string;
    //   age: number;
    // } | number

     

     

    인터페이스 확장/상속

    - 다른 인터페이스를 상속 받아 하나의 인터페이스로 프로퍼티를 정의하도록 도와주는 문법

    - 프로퍼티 확장이 가능하다.

    // 인터페이스 확장
    interface Animal {
      name: string;
      age: number;
    }
    
    interface Dog extends Animal {
      // name: number; // error - 타입 재정의는 불가
      isBark: boolean; // 별칭 확장
    }
    
    interface Cat extends Animal {
      isScratch: boolean;
    }
    
    interface Chicken extends Animal {
      isFly: boolean;
    }
    
    interface DogCat extends Dog, Cat { // 다중 확장
    }

     

     

    인터페이스 합치기

    - 동일한 이름의 인터페이스끼리 프로퍼티가 합쳐진다.

    - 프로퍼티가 이름은 같지만 타입이 다른 경우 충돌이 발생하려 오류를 표출한다.

    // 인터페이스 합치기
    interface Person {
      name: string;
    }
    
    interface Person {
      // name: number; // error - 프로퍼티가 중복되는 별칭은 타입이 다르면 오류가 발생한다.
      age: number;
    }
    
    const person: Person = { // merge
      name: "kim",
      age: 27,
    };

     

     

    클래스

     

    제너릭

    - 함수, 인터페이스, 클래스 등을 다양한 타입과 함께 사용할 수 있게 해주는 타입스크립트 문법

    제너릭 함수

    : 함수에 인수에 따라 원하는 반환 값 타입을 정해서 사용할 수 있다.

    * 함수 명 뒤 <T> 선언, 매개 변수 반환 타입 T 를 작성

    // 제너릭
    function func1(value: unknown) {
      return value;
    }
    let num1 = func1(10);
    // num1.toFixed(); // error - unknown 연산 불가
    if (typeof num1 === "number") {
      num1.toFixed();
    }
    let str1 = func1("string");
    // str.toUpperCase(); // error - unknown 연산 불가
    
    // 제너릭 함수
    function func2<T>(value: T): T {
      return value;
    }
    let num2 = func2(10);
    num2.toFixed();
    let str2 = func2("string");
    str2.toUpperCase();
    let arr = func2<[number, number, number]>([1, 2, 3]);

     

     

    제너릭 함수 활용

    // 1 :  2개의 타입을 사용하는 경우
    function swap<T, U>(a: T, b: U) {
      return [b, a];
    }
    const [a, b] = swap("1", 2);
    
    // 2 : 다양한 배열 타입을 인수로 받는 경우
    function returnFirstValue1<T>(data: T[]) {
      return data[0];
    }
    let num = returnFirstValue1([0, 1, 2]); // number
    let str = returnFirstValue1([1, "hello", "mynameis"]); // number | string
    
    // 3 : 첫번째 요소만 타입을 사용하고, 나머지는 상관 없는 경우
    function returnFirstValue2<T>(data: [T, ...unknown[]]) {
      return data[0];
    }
    let str2 = returnFirstValue2([1, "hello", "mynameis"]); // number
    
    // 4 : 타입 변수 제한하는 경우
    function getLength<T extends { length: number }>(data: T) {
      return data.length;
    }
    getLength("123");
    getLength([1, 2, 3]);
    getLength({ length: 1 });
    // getLength(undefined); // error - T가 length를 가지고 있지 않아서
    // getLength(null); // error - T가 length를 가지고 있지 않아서

     

    map, forEach 타입 함수 작성해보기

    - map, forEach 함수를 제너릭 문법을 사용하여 직접 작성

    - vscode에서 map, forEach 함수에 Alt 또는 Ctrl 클릭 후 마우스를 가져다 대면 타입스크립트에서 정의한 제너릭을 볼 수 있다.

    // map
    const arr = [1, 2, 3];
    const newArr = arr.map((it) => it * 2);
    
    function map<T, U>(arr: T[], callback: (item: T) => U): U[] {
      let result = [];
      for (let i = 0; i < arr.length; i++) {
        result.push(callback(arr[i]));
      }
      return result;
    }
    map(arr, (it) => it.toString());
    
    // forEach
    const arr2 = [1, 2, 3];
    const newArr2 = arr2.forEach((it) => console.log(it));
    
    function forEach<T>(arr: T[], callback: (item: T) => void) {
      for (let i = 0; i < arr.length; i++) {
        callback(arr[i]);
      }
    }
    forEach(arr2, (it) => it.toFixed());
    forEach(['a', 'b', 'c'], (it) => it.toUpperCase());

     

    제너릭 인터페이스, 제너릭 별칭

    - 제너릭은 인터페이스에서 적용이 가능하다.

    - 인덱스 시그니터와 같이 사용하면, 더 유연한 타입 지정이 가능하다.

    - 타입 별칭에서 제너릭 문법을 사용할 수 있다.

    // 제너릭 인터페이스
    interface KeyPair<K, V> {
      key: K;
      value: V;
    }
    let keyPair: KeyPair<string, number> = {
      key: "key",
      value: 0,
    };
    let keyPair2: KeyPair<boolean, string[]> = {
      key: true,
      value: ["1"],
    };
    
    // 인덱스 시그니처와 같이 사용하기
    interface Map<V> {
      [key: string]: V;
    }
    let stringMap: Map<string> = {
      key: "value",
    };
    let booleanMap: Map<boolean> = {
      key: true,
    };
    
    // 타입 별칭으로 제너릭 지정
    type Map2<V> = {
      [key: string]: V;
    };
    let stringMap2: Map2<string> = {
      key: "string",
    };
    
    // 활용
    interface Developer {
      type: "developer";
      skill: string;
    }
    // interface User {
    //   name: string;
    //   profile: Student | Developer;
    // }
    interface User<T> {
      name: string;
      profile: T;
    }
    
    function func(user: User<Developer>) {
      console.log(`${user.profile.type} study`);
    }
    
    // const developerUser: User = {
    //   name: "kim",
    //   profile: {
    //     type: "developer",
    //     skill: "typescript",
    //   },
    // };
    const developerUser: User<Developer> = {
      name: "이정환",
      profile: {
        type: "developer",
        skill: "TypeScript",
      },
    };

     

     

    제너릭 클래스

    - 클래스의 이름 뒤에 타입 변수를 선언하면 제네릭 클래스

    - 생성자에 인수로 전달하는 값이 있을 경우 타입 변수에 할당할 타입을 생략 가능

    // 제너릭 클래스
    // class NumberList {
    //   constructor(private list: number[]) { }
    
    //   push(data: number) {
    //     this.list.push(data);
    //   }
    
    //   pop() {
    //     return this.list.pop();
    //   }
    
    //   print() {
    //     console.log(this.list);
    //   }
    // }
    // const numberList = new NumberList([1, 2, 3]);
    
    class List<T> {
      constructor(private list: T[]) { }
    
      push(data: T) {
        this.list.push(data);
      }
    
      pop() {
        return this.list.pop();
      }
    
      print() {
        console.log(this.list);
      }
    }
    const numberList = new List([1, 2, 3]); // new List<number>([1, 2, 3]); 동일한 코드
    const stringList = new List(["1", "2"]); //  new List<string>(["1", "2"]);

     

     

    프로미스(Promise) 제너릭

    - promise는 제너릭 클래스로 구현되어 있어서, 변수에 할당한 타입으로  resolve 타입이 결정된다.

    - 그러나, reject 인수에 전달하는 값(실패 값)타입은 정의할 수 없고 자동으로 추론하지 못하고  unknown으로 고정된다.

    // Promise
    // 1. 기본 타입
    const promise = new Promise<number>((resolve, reject) => {
      setTimeout(() => {
        // 결과값 : 20
        resolve(20);
      }, 3000);
    });
    promise.then((response) => {
      // response는 number 타입
      console.log(response);
    });
    promise.catch((error) => {
      if (typeof error === "string") {
        console.log(error);
      }
    });
    
    // 2. 객체 변수
    interface Post {
      id: number;
      title: string;
      content: string;
    }
    function fetchPost(): Promise<Post> {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve({
            id: 1,
            title: "게시글 제목",
            content: "게시글 본문",
          });
        }, 3000);
      });
    }
    const PostRequest = fetchPost();
    
    PostRequest.then((response) => {
      console.log(response.id);
    });
    promise.catch((error) => {
      if (typeof error === "string") {
        console.log(error);
      }
    });

     

     

    출처

    • 한 입 크기로 잘라먹는 타입스크립트(TypeScript)
    728x90