
📚 제네릭(Generic)
제네릭(Generic)은 데이터 타입을 일반화(generalize)한다는 것을 뜻한다.
제네릭은 타입을 미리 정하지않고 사용하는 시점에서 타입을 정의할 수 있다.
즉, 선언 시점이 아니라 생성시점에 타입을 명시하기 때문에 하나의 타입만이 아닌 여러 타입을 사용할 수 있는 것이다.
📌 왜 제네릭을 사용할까?
선언시점이 아닌 생성 시점에 타입을 명시하기 때문에, 한번의 선언으로 다양한 타입에 '재사용'이 가능하다는 장점이 있다.
제네릭을 사용하지 않는다면?

선언 시점에 타입을 미리 정하거나, any를 사용해야할 것이다.
1) 타입을 미리 정하면, 하나의 타입만 사용하기 때문에 코드의 범용성이 떨어진다.
2) any를 사용하면, 데이터 타입을 제한할 수 없으며 어떤 데이터가 리턴될 지 알지 못한다.
이런 경우에 제네릭을 활용 할 수 있다.
📌 제네릭 사용하기
1. 제네릭 기본 예제
function logText<T>(text: T): T {
return text;
}
<T>(text: T): T
여기서 <T>
안의 T를 타입변수라고 한다.
logText 함수는 T 타입변수라는 타입변수를 가진 것이다. 그리고 text와 return의 타입 또한 동일한 타입변수 T를 갖고 있는 것이다.
여기서 length 속성을 확인하는 코드를 사용하려면
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
에러가 난다. 지금 코드는 any와 같이 입력값과 반환값에 모든 타입이 들어올 수 있다.
에러가 나는 것은 length 속성이 없는 number가 들어왔을 땐 유효하지 않기 때문이다.
이런 경우에는 다음과 같이 세부적으로 타입을 줄 수 있다.
function logText<T>(text: T[]): T[] {
console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용합니다.
return text;
}
T라는 타입변수를 받고 배열형태로 입력값을 받고 있다. 따라서 logText([1, 2, 3])
과 같은 형태로 사용할 수 있다.
이런 방식으로 제네릭을 사용하면 유연한 방식으로 함수의 타입을 정의 해줄 수 있는 것이다.
또는 다음과 같이 명시적으로 제네릭 타입을 선언할 수 있다.
function logText<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
2. 클래스에서 제네릭 사용
다음 예시는 class의 속성에 제네릭 타입 P를 선언하고 있다.
class User<P> {
constructor(public payload: P) {}
getPayload() {
return this.payload;
}
}
그리고 인스턴스의 인터페이스를 정의해줬다.
interface UserAType {
name: string;
age: number;
isValid: boolean;
}
interface UserBType {
name: string;
age: number;
emails: string[];
}
그리고 클래스의 인스턴스를 생성했다.
const heropy = new User<UserAType>({
name: "Heropy",
age: 20,
isValid: true,
});
const neo = new User<UserBType>({
name: "Neo",
age: 11,
emails: ["neo@gmail.com"],
});
인스턴스가 생성될 때 인자의 타입 UserAType, UserBType를 제네릭 P로 전달한다.
따라서 각 타입에 맞게끔 클래스에서 동적으로 객체타입을 지정하고 반환한다.
3. 인터페이스에서 제네릭 사용
interface MyData<T> {
name: string;
value: T;
}
const dataA: MyData<string> = {
name: "Data A",
value: "Hello world!",
};
const dataB: MyData<number> = {
name: "Data B",
value: 1234,
};
📌 제네릭 타입 제약
제네릭 타입 제약은 제네릭으로 타입을 정의할 때 좀 더 정확한 타입을 정의할 수 있게 도와주는 문법이다.
일반적으로 타입제약 시 여러 개의 타입 중 몇 개만 사용할 수 있도록 제약한다.
1. 상속(extends) 사용한 타입 제약
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
앞서 봤던 코드를 extend를 이용해서 좀 더 세부적으로 타입 제약을 줄 수 있다.
function logText<T extends { length: number }>(text: T) {
return text.length
}
length 속성을 가지고 있는 객체를 extends한 타입만 입력값으로 받을 수 있도록 제약을 둘 수 있다.
2. keyof를 사용한 타입 제약
✍️ keyof 란?
keyof 연산자는 객체 타입에서 객체의 키 값들을 숫자나 문자열 리터럴 유니언을 생성합니다. 아래 타입 P는 “x” | “y”와 동일한 타입입니다.
- 타입스크립트 공식문서-
쉽게 말해 특정 타입의 키 값을 추출해서 문자열 유니언 타입으로 변환해준다.
type Point = { x: number; y: number };
type P = keyof Point; // "X" | "Y"
다시 돌아가서, 제네릭 타입 제약으로 keyof를 적용한 예제를 보자.
function printKeys<T extends keyof { name: string; skill: string }>(value: T) {
return value
}
제네릭을 extends와 keyof을 사용해서 name과 skill 속성만 갖는 객체의 키 타입만 받겠다고 정의한 것이다.
타입변수를 입력값에도 연결 했기 때문에, 입력값에는 "name"과 "string"만 들어 올 수 있는 것이다.
+) 사실 이 부분이 바로 이해가 안됐다.
예제로 쓰인 printKeys 함수의 목적은 주어진 객체의 속성 키를 추출하는 것이라고 생각하면 된다.
Reference

📚 제네릭(Generic)
제네릭(Generic)은 데이터 타입을 일반화(generalize)한다는 것을 뜻한다.
제네릭은 타입을 미리 정하지않고 사용하는 시점에서 타입을 정의할 수 있다.
즉, 선언 시점이 아니라 생성시점에 타입을 명시하기 때문에 하나의 타입만이 아닌 여러 타입을 사용할 수 있는 것이다.
📌 왜 제네릭을 사용할까?
선언시점이 아닌 생성 시점에 타입을 명시하기 때문에, 한번의 선언으로 다양한 타입에 '재사용'이 가능하다는 장점이 있다.
제네릭을 사용하지 않는다면?

선언 시점에 타입을 미리 정하거나, any를 사용해야할 것이다.
1) 타입을 미리 정하면, 하나의 타입만 사용하기 때문에 코드의 범용성이 떨어진다.
2) any를 사용하면, 데이터 타입을 제한할 수 없으며 어떤 데이터가 리턴될 지 알지 못한다.
이런 경우에 제네릭을 활용 할 수 있다.
📌 제네릭 사용하기
1. 제네릭 기본 예제
function logText<T>(text: T): T {
return text;
}
<T>(text: T): T
여기서 <T>
안의 T를 타입변수라고 한다.
logText 함수는 T 타입변수라는 타입변수를 가진 것이다. 그리고 text와 return의 타입 또한 동일한 타입변수 T를 갖고 있는 것이다.
여기서 length 속성을 확인하는 코드를 사용하려면
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
에러가 난다. 지금 코드는 any와 같이 입력값과 반환값에 모든 타입이 들어올 수 있다.
에러가 나는 것은 length 속성이 없는 number가 들어왔을 땐 유효하지 않기 때문이다.
이런 경우에는 다음과 같이 세부적으로 타입을 줄 수 있다.
function logText<T>(text: T[]): T[] {
console.log(text.length); // 제네릭 타입이 배열이기 때문에 `length`를 허용합니다.
return text;
}
T라는 타입변수를 받고 배열형태로 입력값을 받고 있다. 따라서 logText([1, 2, 3])
과 같은 형태로 사용할 수 있다.
이런 방식으로 제네릭을 사용하면 유연한 방식으로 함수의 타입을 정의 해줄 수 있는 것이다.
또는 다음과 같이 명시적으로 제네릭 타입을 선언할 수 있다.
function logText<T>(text: Array<T>): Array<T> {
console.log(text.length);
return text;
}
2. 클래스에서 제네릭 사용
다음 예시는 class의 속성에 제네릭 타입 P를 선언하고 있다.
class User<P> {
constructor(public payload: P) {}
getPayload() {
return this.payload;
}
}
그리고 인스턴스의 인터페이스를 정의해줬다.
interface UserAType {
name: string;
age: number;
isValid: boolean;
}
interface UserBType {
name: string;
age: number;
emails: string[];
}
그리고 클래스의 인스턴스를 생성했다.
const heropy = new User<UserAType>({
name: "Heropy",
age: 20,
isValid: true,
});
const neo = new User<UserBType>({
name: "Neo",
age: 11,
emails: ["neo@gmail.com"],
});
인스턴스가 생성될 때 인자의 타입 UserAType, UserBType를 제네릭 P로 전달한다.
따라서 각 타입에 맞게끔 클래스에서 동적으로 객체타입을 지정하고 반환한다.
3. 인터페이스에서 제네릭 사용
interface MyData<T> {
name: string;
value: T;
}
const dataA: MyData<string> = {
name: "Data A",
value: "Hello world!",
};
const dataB: MyData<number> = {
name: "Data B",
value: 1234,
};
📌 제네릭 타입 제약
제네릭 타입 제약은 제네릭으로 타입을 정의할 때 좀 더 정확한 타입을 정의할 수 있게 도와주는 문법이다.
일반적으로 타입제약 시 여러 개의 타입 중 몇 개만 사용할 수 있도록 제약한다.
1. 상속(extends) 사용한 타입 제약
function logText<T>(text: T): T {
console.log(text.length); // Error: T doesn't have .length
return text;
}
앞서 봤던 코드를 extend를 이용해서 좀 더 세부적으로 타입 제약을 줄 수 있다.
function logText<T extends { length: number }>(text: T) {
return text.length
}
length 속성을 가지고 있는 객체를 extends한 타입만 입력값으로 받을 수 있도록 제약을 둘 수 있다.
2. keyof를 사용한 타입 제약
✍️ keyof 란?
keyof 연산자는 객체 타입에서 객체의 키 값들을 숫자나 문자열 리터럴 유니언을 생성합니다. 아래 타입 P는 “x” | “y”와 동일한 타입입니다.
- 타입스크립트 공식문서-
쉽게 말해 특정 타입의 키 값을 추출해서 문자열 유니언 타입으로 변환해준다.
type Point = { x: number; y: number };
type P = keyof Point; // "X" | "Y"
다시 돌아가서, 제네릭 타입 제약으로 keyof를 적용한 예제를 보자.
function printKeys<T extends keyof { name: string; skill: string }>(value: T) {
return value
}
제네릭을 extends와 keyof을 사용해서 name과 skill 속성만 갖는 객체의 키 타입만 받겠다고 정의한 것이다.
타입변수를 입력값에도 연결 했기 때문에, 입력값에는 "name"과 "string"만 들어 올 수 있는 것이다.
+) 사실 이 부분이 바로 이해가 안됐다.
예제로 쓰인 printKeys 함수의 목적은 주어진 객체의 속성 키를 추출하는 것이라고 생각하면 된다.