[TypeScript] 섹션7. 제네릭(1)

jaehoon ahn·2025년 2월 13일

TypeScript

목록 보기
10/14
post-thumbnail

제네릭 소개

제네릭이란

function func(value: string) {
  return value;
}
func(10);
func(true);
func(10);
func(true);

⇒ 이렇게 number, boolean을 넣고싶을 때?

⇒ 이럴 때 사용하면 좋은 것이 제네릭

  • 제네릭 함수로 만들어주면 원하는 데로 인수의 따라서 반환값의 타입을 가변적으로 바꿀 수 있다.
  • 제네릭: 일반적인, 포괄적인
  • 제네릭은 모든 타입에 두루두루 사용할 수 있는 범용적인 함수이다.

사용방법


function func<T>(value: T): T {
  return value;
}
let num = func(10); // number 타입으로 추론됌
let bool = func(true); // boolean 타입으로 추론됌
let str = func("string"); // string 타입으로 추론됌
  • 는 타입 변수라고 한다. 즉, 타입을 담는 변수이다.
  • 상황에 따라 타입을 담는다.
  • 함수를 호출할 때마다 타입이 결정이 된다.
  • 타입 변수와 함께 여러 타입의 값을 인수로 받아서 범용적으로 사용할 수 있는 함수 ⇒ 이를 제네릭 함수라고 한다.

명시적으로 타입 정하기(튜플)

let arr2 = func<[number, number, number]>([1, 2, 3]); 
//  튜플 타입으로 할당하기. T의 타입은 튜플로 할당됌.

타입 변수 응용하기

첫 번째 사례

function swap<T>(a: T, b: T) {
  return [b, a];
}

const [a, b] = swap("1", 2); // 첫 번째 매개변수를 string으로 정의하면 오류가 발생
// T는 string 타입으로 할당 -> 2는 number 타입, 이미 T는 string 타입으로 할당됌
// number와 string 타입이 달라서 오류 발생
// 이럴 때는 타입 변수를 2개를 사용하면 됌

해결방법

function swap<T, U>(a: T, b: U) {
  return [b, a];
}
const [a, b] = swap("1", 2);
  • T는 string, U는 number 타입으로 할당되서 오류 해결
  • 타입 변수를 필요한 상황에는 여러 개 사용할 수 있다.

두 번째 사례

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

let num = returnFirstValue([0, 1, 2]);

let str = returnFirstValue([1, "hello", "mynameis"]); 
// number | string 유니온 타입으로 추론됌

⇒ 첫 번째 요소를 바꾸더라도 number 타입으로만 추론이 됐으면 할 때

function returnFirstValue<T>(data: [T, ...unknown[]]) {
  return data[0];
}
let num = returnFirstValue([0, 1, 2]);

let str = returnFirstValue([1, "hello", "mynameis"]);
Tnumber로 할당됌

세 번째 사례

function getLength(data: any) {
  return data.length;
}
let var1 = getLength([1, 2, 3]); // 3
let var2 = getLength(["12345"]); // 5
let var3 = getLength({ length: 10 }); // 10

let var4 = getLength(10); // 오류 발생(x)
// var1~3처럼 length가 있는 것은 받고, var4처럼  
// length가 존재하지 않는 것은 받지 않는 법

⇒ var1~3처럼 length가 있는 것은 받고, var4처럼 lenght가 존재하지 않는 것은 받지 않는 방법

해결 방법

function getLength<T extends { length: number }>(data: T) {
  return data.length;
}
let var1 = getLength([1, 2, 3]); // 3
let var2 = getLength(["12345"]); // 5
let var3 = getLength({ length: 10 }); // 10

let var4 = getLength(10); // 오류 발생

⇒ number 타입의 lenght를 가지고 있는 객체를 확장하는 타입으로 T를 제한

map, forEach 메서드 타입 정의하기

map 메서드

map 예시

const arr = [1, 2, 3];
const newArr = arr.map((it) => it * 2);
// [2, 4, 6];

map 정의

function map<T>(arr: T[], callback: (item: T) => T) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(callback(arr[i]));
  }
  return result;
}
map(arr, (it) => it * 2); // number 타입으로 추론됌
map(["hi", "hello"], (it) => it.toUpperCase()); // string 타입으로 추론됌
map(["hi", "hello"], (it) => parseInt(it)); // 오류발생
// 콜백 함수의 반환값이 number가 됌
// arr 매개변수에 string 배열 타입이 들어감
// T는 string 타입이 됌
// 콜백함수의 매개변수의 타입도 string 타입이 됌
// 콜백함수의 반환값 타입도 string이 됌
// 하지만, parseInt 때문에 number 타입으로 전달되서 매치가 안되서 오류가 발생

문제 해결

// 제네릭 타입 변수를 하나 말고, 2개 타입 변수를 써주면 된다.
function map<T, U>(arr: T[], callback: (item: T) => U) {
  let result = [];
  for (let i = 0; i < arr.length; i++) {
    result.push(callback(arr[i]));
  }
  return result;
}
map(arr, (it) => it * 2); // number 타입으로 추론됌
map(["hi", "hello"], (it) => it.toUpperCase()); // string 타입으로 추론됌
map(["hi", "hello"], (it) => parseInt(it)); // 오류발생
// arr에 string 배열 타입이 들어감 => T = string타입
// 콜백함수의 item T도 string 타입
// 이때 콜백함수의 반환값의 타입은 number 타입이 됌.
// U의 타입은 이때 추론이 되어 number 타입이 됌.

forEach

const arr2 = [1, 2, 3];
arr2.forEach((it) => console.log(it));

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

forEach(arr2, (it) => {
  console.log(it.toFixed());
});

forEach(["123", "456"], (it) => {
  it;
});

0개의 댓글