티스토리 뷰

제네릭

제네릭이란 함수, 인터페이스, 타입별칭, 클래스 등을 다양한 타입과 함께 동작하도록 만들어주는 기능이다.

 

1. 제네릭 함수

두루두루 모든 타입의 값을 다 적용할 수 있는 범용적인 함수라고 이해하면 된다.


function func<T>(value: T) {
  return value;
}

let num2 = func(10);
let bool2 = func(true);
let str2 = func("String");

타입을 지정하기 위해 타입변수 T<T>로 정의하고 이에 따른 반환값과 매개변수의 타입을 가변적으로 지정해준다.

 

 

참고로 제네릭 함수를 호출할 때 아래와 같이 타입변수에 할당할 타입을 직접 명시하는 것도 가능하다.

let arr = func<[number, number, number]>([1, 2, 3]);

.

2. 타입 변수 응용

여러개의 타입변수 선언하기

매개변수의 타입이 다양한 경우에는 어떻게 활용할까?

아래와 같이 필요한만큼 타입 변수를 사용하면 된다.


function swap<T, U>(a: T, b: U) {
  return [b, a];
}
const [a, b] = swap(1, "2");

 

다양한 배열 타입을 매개변수로 받기

다양한 배열 타입을 인수로 받는 제네릭 함수를 만드는 방법은 아래와 같다.


function returnFirstValue<T>(data: T[]) {
  return data[0];
}

let temp1 = returnFirstValue([0, 1, 2]);
let temp2 = returnFirstValue(["hello", "hi"]);
let temp3 = returnFirstValue(["hello", 1234, true]); // union

위 예시에서 temp3의 경우 배열 안에 string, number, boolean이 전부 들어가 있으므로 이때의 반환값은 union 타입이 된다.

 

 

만일 제대로 타입 추론하게 하고 싶다면, 아래와 같이 매개변수를 튜플로 정의하면 된다.
이때 필요한 매개변수의 타입만 타입변수로 명시하고 나머지 매개변수는 unknown으로 추정한다.


function rFV<T>(data: [T, ...unknown[]]) {
  return data[0];
}

// 맨 앞의 원소의 타입(string)을 정확하게 추론함
let temp4 = rFV(["hello", 1234, true]);

 

매개변수의 타입 제한하기

함수로 전달할 타입 변수를 제한하는 것도 가능하다. 이때 타입 변수를 제한한다는 것은 함수를 호출하고 인수로 전달할 수 있는 값의 범위에 제한을 두는 것을 의미한다.

 

아래의 코드는 타입 변수를 length 프로퍼티가 number 값으로 존재하는 객체 타입으로 제한한 예시이다. 타입 변수를 제한할 때에는 extends 키워드를 활용한다.


function getLength<T extends { length: number }>(data: T) {
  return data.length;
}

let v1 = getLength([1, 2, 3]); // 3
let v2 = getLength("12345"); // 5
let v3 = getLength({ length: 10 }); // 10
// let v4 = getLength(10); // 에러 ; length 프로퍼티 존재하지 않음

 

3. map, forEach 메서드 타입 정의

자바스크립트의 map, ForEach 메서드를 직접 타입스크립트로 구현해보자.

 

map

원본 배열의 각 요소에 콜백함수를 수행하고 반환된 값들을 모아 새로운 배열로 반환하는 메서드이다.


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;
}
const arr = [1, 2, 3];
map(arr, (it) => it * 2);
map(["hello", "string"], (it) => it.toUpperCase()); // T, U : string
map(["hello", "string"], (it) => parseInt(it)); // T : string , U : number

원본 배열의 타입과 다른 타입의 배열로도 반환할 수 있도록, 타입 변수를 2개(T,U) 활용하였다.

 

ForEach

forEach는 배열의 모든 요소에 콜백함수를 한 번씩 수행해주는 메서드이다. 참고로 forEach는 반환값이 없는 메서드이므로 콜백 함수의 반환값 타입void로 정의한다.


function forEach<T>(arr: T[], callback: (item: T) => void) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i]);
  }
}

forEach([1, 2, 3], (it) => {
  console.log(it.toFixed());
});

forEach(["123", "456"], (it) => {
  console.log(it.toUpperCase());
});

 

제네릭 기타

1. 제네릭 인터페이스

제네릭은 인터페이스에서도 적용할 수 있다. 다음과 같이 인터페이스에 타입 변수를 선언하면 된다.


interface KeyPair<K, V> {
  key: K;
  value: V;
}

 

제네릭 인터페이스는 일반적인 인터페이스와 마찬가지로 변수의 타입으로 정의할 수 있다.
변수의 타입으로 사용할 때에는 타입변수에 할당할 타입을 명시해야 한다.


let keyPair: KeyPair<string, number> = {
  key: "key",
  value: 123,
};

let keyPair2: KeyPair<boolean, string[]> = {
  key: true,
  value: ["h", "e", "l"],
};

 

인덱스 시그니쳐

제네릭 인터페이스는 인덱스 시그니쳐와 함께 사용하면 더욱 유연한 객체 타입을 정의할 수 있다.

  • 인덱스 시그니쳐 : 프로퍼티에 key와 value의 타입과 관련된 규칙만 만족하면 어떤 객체든 허용하는 유연한 문법
// 일반적인 인터페이스의 인덱스 시그니쳐
interface NumberMap {
  [key: string]: number;
}

let temp1: NumberMap = {
  myKey: -345,
  myKey2: 5558,
};

// 제너릭 인터페이스의 인덱스 시그니쳐
interface Map<V> {
  [key: string]: V;
}

let stringMap: Map<string> = {
  mykey: "hello",
  myKey2: "hell",
};

let numberMap: Map<number> = {
  myKey: 123,
  myKey2: 456,
};

 

2. 제네릭 타입 별칭

타입 별칭에도 역시 제네릭을 활용할 수 있다.


type Map2<V> = {
  [key: string]: V;
};

let booleanMap: Map2<boolean> = {
  key: true,
  key2: true,
};

 

3. 제네릭 인터페이스 활용예시

아래 예시는 개발자 또는 학생이 이용하는 프로그램을 구현한 것이다. 제네릭 인터페이스를 활용하면 불필요한 타입 좁히기를 방지하고 간결한 코드를 짤 수 있다.


interface Student {
  type: "std";
  school: string;
}

interface Developter {
  type: "dev";
  skill: string;
}

interface User<T> {
  name: string;
  profile: T;
}

// std user가 사용할 메서드
function goToSchool(user: User<Student>) {
  console.log(`${user.name}씨가 ${user.profile.school}에 갑니다`);
}

const devUser: User<Developter> = {
  name: "홍길동",
  profile: {
    type: "dev",
    skill: "typescript",
  },
};

const stdUser: User<Student> = {
  name: "김길동",
  profile: {
    type: "std",
    school: "highschool",
  },
};

goToSchool(stdUser);

객체 타입들로 조합된 복잡한 객체 타입을 정의해서 사용해야 할 때, 제네릭 인터페이스를 사용하면 깔끔하게 타입을 분류하고 관리할 수 있다. 

 

4. 제네릭 클래스

클래스도 제네릭과 함께 사용하여 여러 타입에서도 적용할 수 있는 범용적 클래스로 정의할 수 있다.

 

생성자에 전달하는 데이터를 통해 타입 변수의 타입이 추론되므로 제네릭 인터페이스나 타입 별칭처럼 앞에 타입 변수를 명시하지 않아도 된다.


class List<T> {
  // 생성자 선언 + 초기화 작업 수행
  constructor(private list: T[]) {}

  // 메서드
  push(data: T) {
    this.list.push(data);
  }
  pop() {
    this.list.pop();
  }
  print() {
    console.log(this.list);
  }
}

const numList = new List([4, 5, 6]);
const stringList = new List(["a", "b", "c"]);
numList.pop();
numList.print();
stringList.push("d");
stringList.print();

 

프로미스와 제네릭

Promise는 제네릭 클래스로 구현되어 있다. 따라서 새로운 Promise를 생성할 때 타입 변수에 할당할 타임을 직접 설정해주면 해당 타입이 바로 resolve의 결과값의 타입이 된다.


const promise = new Promise<number>((resolve, reject) => {
  // 3초뒤 20 반환하는 비동기 작업
  setTimeout(() => {
    // resolve(20);// 작업 성공
    reject("~에 실패"); // 작업 실패
  }, 3000);
});

promise.then((response) => {
  console.log(response * 10); // 20
});
promise.catch((error) => {
  if (typeof error === "string") {
    console.log(error);
  }
});

 

만약 Promise 객체를 반환한다면 함수의 반환값 타입을 Promise<타입> 같이 직관적으로 정의할 수 있다.


interface Post {
  id: number;
  title: string;
  content: string;
}

function fetchPost(): Promise<Post> {
  // 3초뒤 게시글 객체를 가져오는 비동기 객체 반환
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 1,
        title: "내 게시글",
        content: "내용입니다",
      });
    }, 3000);
  });
}

const postRequest = fetchPost();
postRequest.then((post) => {
  post.id;
});

 

혹은 다음과 같이 반환할 Promise 객체를 반환할 때 타입을 직접 명시하는 방법도 있다.

function fetchPost(): {
  return new Promise<Post>((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 1,
        title: "내 게시글",
        content: "내용입니다",
      });
    }, 3000);
  });
}

본 포스팅은 인프런 한 입 크기로 잘라먹는 타입스크립트 강의를 기반으로 작성되었습니다. 사용된 이미지들은 모두 위 강의에서 가져왔습니다. 문제시 알려주세요.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함