반응형
타입을 일반화하여 코드 재사용성과 타입 안정성을 동시에 얻을 수 있게 해주는 도구
함수를 작성할 때 타입을 고정하지 않고, 나중에 호출하는 시점에 타입을 지정할 수 있도록 만든 타입 매개변수
T는 타입의 placeholder (임시 변수)입니다. 이 함수는 string, number, boolean 등 어떤 타입이든 받을 수 있고, 그 타입 그대로 반환합니다.

제네릭을 쓰는 이유?

예문
function echo<T>(input: T): T { // 함수에도 <T>, 입력값에도 T, 결과값에도 T
console.log(typeof input, input); // 출력값이랑 출력값의 타입을 알고 싶다
return input;
}
echo<string>("Hello"); // string "Hello"
echo<number>(123); // number 123

※ Identity 함수
일반적으로 함수는 이렇게 쓰고 다니지만,
function identityNumber(value: number): number {
return value;
}
Identity 함수에 제네릭을 선언해서 받으면, 결과값도 받을 당시의 타입 그대로 출력한다.
function identity<T>(value: T): T {
return value;
}
※ 이중 제네릭 - 객체 (K: 키 key, V: 값 value)
// 제네릭 타입을 받는 함수의 기본형부터 다시 보기
function '함수 이름'<제네릭 타입 선언>(받을 인자 선언): 결과값 선언 {
return 출력할 무언가;
}
// 제네릭 타입, 받을 인자, 결과값 전부 제네릭이 들어가야 함.
function getValue<K extends string, V>(obj: Record<K, V>, key: K): V {
return obj[key];
}
// 상위 객체 생성 (name: string, age: <T>)
const objects = { name: "john", age: 20 };
// 객체 생성 - getValue 함수에 obj인자(위에서 만든 객체인 objects)랑 key인자(객체의 속성) 넣음.
let result1 = getValue(objects, "name");
let result2 = getValue(objects, "age");
console.log(`${result1}, ${result2}세`);
함수 선언부분을 차근차근 뜯어보자.
- 제네릭에서의 extends는 클래스 상속이 아니라 타입 제약으로 쓰임.
- K는 string(혹은 string 하위, 문자열 리터럴 타입)으로 타입을 제한하고, V는 제한하지 않음.
- Record<K, V>는 TypeScript의 유틸리티 타입 (= "키가 K 타입이고, 값이 V 타입인 객체")
※ Record<K, V> 가 뭔데?
// Record<K, V> 아래와 같은 역할을 한다.
type Record<K extends string | number | symbol, V> = {
[P in K]: V;
}
// [K]배열 안에 P가 있는지 확인하는 연산자.
// 아래처럼 K, V 각각 타입을 지정하고, 그 위에 객체까지 만들어주는 기능이라고 보면 된다.
type K = string | number | symbol
type V = any
type MyRecord = { [P in K]: V }
//결과
{ [key: string]: valueType }
- 위에서 'obj: Record<K, V>'라고 했으니, 받을 인자 중 하나인 obj는 K, V 쌍으로 받으면서 객체를 만드는 타입이구나~
※ 그럼 key: K는 뭐야?
- 받을 인자가 obj(: obj의 타입)랑 key라서 함수를 호출할 때 'getValue(obj, key)'의 key 타입으로 K를 받겠다는 말인데,
- 바로 앞에서 이해했듯 { K : V } 중에서 K를 넣어서 value값을 호출하겠다는 말인 것이다.
제네릭 인터페이스
// interface도 제네릭 타입으로 받을 수 있음.
interface Box<T> {
value: T;
}
const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };
// 키인 'value'에 직접 접근
console.log(`${stringBox.value}, No.${numberBox.value}`);

제네릭 클래스
// 클래스에도 제네릭 타입
class Stack<T> {
private items: T[] = [];
// push로 밀어 넣을 값의 타입도 제네릭
push(item: T) {
this.items.push(item);
}
// pop으로 꺼내 먹을 값의 타입도 제네릭, 없으면 undefined로 예외 처리
pop(): T | undefined {
return this.items.pop();
}
}
// 객체 생성할 때 클래스<타입>을 써서 생성. <타입>은 뭐든 바꿀 수 있음.
const numberStack = new Stack<number>();
// 메서드를 실행할 때에도 위 클래스에서 정의했듯, ()안에 들어갈 값의 타입은 바꿀 수 있음.
// 하지만 바로 위에서 객체를 생성할 때의 타입과 같아야 함.
numberStack.push(1);
numberStack.push(2);
console.log(numberStack);
숫자로 했을 때,

문자열로 했을 때, (※ 객체 생성시 <number>를 <string>으로 변경)

실전 예제
// ApiResponse에 대해서 data 속성만 제네릭 타입으로 받는 interface 설정
interface ApiResponse<T> {
status: number;
message: string;
data: T;
}
// userResponse라는 객체 생성 - 위 interface 에서 제네릭 타입으로 받은 걸
// 객체에서도 당연히 interface를 따를 거니까, stat이랑 mesg는 정해진 타입대로 받을거고, data도 받을건데,
// 위에서 제네릭 타입으로 설정했던 data 속성에서
// name은 string타입으로, age는 number타입으로 해서 {키 : 밸류} 형태로 받겠다
const userResponse: ApiResponse<{ name: string; age: number }> = {
status: 200,
message: "Success",
data: {
name: "Alice",
age: 25,
},
};
console.log(userResponse);

제네릭 너무 어려웡... ㅠ
반응형
'Front-End > TypeScript' 카테고리의 다른 글
| TypeScript #13 (OOP 연습 문제 #1~10) (0) | 2025.07.03 |
|---|---|
| TypeScript #12 (OOP 예제 #1~5) (0) | 2025.07.03 |
| TypeScript #11 (객체 지향) (0) | 2025.07.03 |
| TypeScript #10 (함수 유효성 검사) (0) | 2025.07.02 |
| TypeScript #9 (함수) (0) | 2025.07.02 |