Generic 이란?
- Generic 은 C#, Java 등의 정적 타입 언어의 경우, 함수 또는 클래스를 정의하는 시점에서 리턴 타입이나 매개변수의 타입을 선언해줘야하는데, 같은 함수여도 매개변수 타입과 리턴 타입이 다른 경우가 있다면 그에 해당하는 함수를 하나하나 다 작성해줘야 합니다.
- 이때, 재사용성이 높은 컴포넌트를 만들어 하나의 함수만으로 사용하고 싶을 때 자주 사용하는 기능이 바로 Generic 입니다.
- Generic 을 사용하면 단일 타입이 아니라 사용하고 싶을 때 마다 그에 맞는 타입으로 사용할 수 있어서 코드를 일일이 작성할 필요가 사라져 코드의 가독성 또한 향상됩니다.
Generic 사용 방법
- Generic 을 사용하기 위해서는 변수명, 함수명 뒤에다가 <T> 를 작성해줍니다. (꼭 T 가 아니어도 되지만, 일반적으로는 Type 을 의미한다는 뜻에서 T 로 작성합니다.)
function getSize<T>(arr: T[]): T {
// T 라는 어떤 타입을 받아서 이 함수에서 사용할 수 있게 해준다.
return arr.length;
}
const arr1 = [1, 2, 3];
console.log(getSize(arr1));
const newArr = ["1", "2", "3"];
console.log(getSize(newArr));
// 자동으로 매개변수의 타입을 추론해서 넣어준다.
// 속성값의 타입을 동적으로 주고 싶을 때
interface Mobile2<T> {
name: string;
price: number;
option: T;
}
const m1: Mobile2<object> = {
name: "s21",
price: 1000,
option: {
color: "red",
coupon: false,
},
};
const m2: Mobile2<string> = {
name: "s20",
price: 800,
option: "good",
};
// extends
interface User4 {
name: string;
age: number;
}
interface Car4 {
name: string;
color: string;
}
interface Book {
price: number;
}
const user4: User4 = {
name: "a",
age: 20,
};
const car4: Car4 = {
name: "bmw",
color: "black",
};
const book: Book = { price: 10000 };
function showName4<T extends { name: string }>(data: T): string {
// 그냥 <T>만 하게되면 T의 타입에 name 이 있는지 없는지 확신할 수 없어서 에러가 뜬다.
// 이 때, T extends 를 사용하게 되면 data 타입으로 T 가 오는데 이것은
{name: string} 를 확장한 형태이다 라고 알려주는 역할이다.
// --> T 는 항상 {name: string} 를 갖고 있는 객체만 받을 수 있다.
return data.name;
}
showName4(user4);
showName4(car4);
// showName4(book) // 에러가 뜬다.
Generic 이 사용되는 Utility Types
- Utility Type 이란, 이미 정의한 타입을 다른 타입으로 변환할 때 사용하는 문법입니다.
- 하나하나씩 살펴보도록 하겠습니다.
1. keyof
- keyof 를 사용하면 key 값들을 모두 유니언 형태로 받습니다.
interface User5 {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
// 여기서 keyof 키워드를 사용하면 User5 의 key값들을 유니언 형태로 받을 수 있다.
type UserKey = keyof User5; // 'id' | 'name' | 'age' | 'gender'
const uk: UserKey = "id";
2. Partial<T>
- Partial 은 프로퍼티를 모두 Optional 로 바꿔줍니다.
interface User5 {
id: number;
name: string;
age: number;
gender: "m" | "f";
}
// 프로퍼티를 모두 옵셔널로 바꿔준다.
// Partial<User5> 를 해주게 되면
// interface User5{
// id?: number
// name?: string
// age?: number
// gender?: 'm' | 'f'
// } 과 같다.
let admin: Partial<User5> = {
id: 1,
name: "Bob",
};
3. Required<T>
- Partial 과 반대로 프로퍼티를 모두 필수 요소로 바꿔줍니다.
4. Readonly<T>
- 프로퍼티를 모두 readonly 로 바꿔줍니다.
5. Record<K,T>
- 여기서 K 는 Key 이고, T는 Type 을 의미합니다.
interface Score1 {
"1": "A" | "B" | "C" | "D";
"2": "A" | "B" | "C" | "D";
"3": "A" | "B" | "C" | "D";
"4": "A" | "B" | "C" | "D";
}
const score: Score1 = {
"1": "A",
"2": "B",
"3": "C",
"4": "D",
};
// 이렇게 작성 가능
const score1: Record<"1" | "2" | "3" | "4", "A" | "B" | "C" | "D"> = {
"1": "A",
"2": "B",
"3": "C",
"4": "D",
};
// 이렇게도 가능
type Grade2 = "1" | "2" | "3" | "4";
type Score2 = "A" | "B" | "C" | "D";
const score2: Record<Grade2, Score2> = {
"1": "A",
"2": "C",
"3": "B",
"4": "D",
};
// 다른 예시
interface User6 {
id: number;
name: string;
age: number;
}
function isValid(user: User6) {
const result: Record<keyof User6, boolean> = {
id: user.id > 0,
name: user.name !== "",
age: user.age > 0,
};
return result;
}
// 해당하는 key 와 type 만 올 수 있다.
6. Pick<T,K>
interface User7 {
id: number;
name: string;
age: number;
gender: "M" | "W";
}
const admin2: Pick<User7, "id" | "name"> = {
id: 3,
name: "jin",
};
7. Omit<T,K>
interface User7 {
id: number;
name: string;
age: number;
gender: "M" | "W";
}
const admin3: Omit<User7, 'gender' | 'age'> = {
id: 3,
name: 'jin'
}
8. Exclude<T1, T2>
type T1 = string | number | boolean
type T2 = Exclude<T1, number | string> // boolean 만 남게 된다.
9. NonNullable<Types>
- null 과 undefined 를 제외한 타입을 생성
type T3 = string | null | undefined | void;
type T4 = NonNullable<T3> // string | void 만 남는다.