본문 바로가기
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