제네릭 타입이란 함수나 클래스의 선언시점이 아닌, 사용 시점에 타입을 선언할 수 있는 방법을 제공합니다.
즉 제네릭은 코드를 작성할 때가 아니라 코드를 수행될 때(런타임) 타입을 명시합니다.
식별자를 써서 아직 정해지지 않는 타입을 표시합니다. (일반적으로 T, K, U 등을 사용)
any
타입을 사용하면 여러 타입을 넣을 수 있습니다.제네릭은 함수의 파라미터를 넣는 것과 같이 사용합니다.
// 어떤 타입을 받을 건지 먼저 정의합니다. (const logText = <T>())
// params 타입으로 정의 (text: T)
const logText = <T>(text: T): T => {
console.log(text);
return text;
}
// 함수를 호출할 때 타입 정의
const str = logText<string>('hello');
str.split("") // string 으로 정의했기 때문에 문자열 함수인 split 가능
const arr = logText<number[]>([1]);
arr.map((a) => a) // number 배열로 정의했기 때문에 배열 함수인 map 가능
// TableColumn 인터페이스 정의: 각 컬럼에 대한 정보를 명시합니다.
interface TableColumn<T> {
title: string; // 테이블 헤더에 표시될 컬럼 이름
key: keyof T; // 데이터 객체에서 해당 컬럼의 값을 찾기 위한 키
render?: (value: any, record: T) => React.ReactNode; // 커스텀 렌더링 함수 (옵션)
}
// GenericTable 컴포넌트 정의
interface GenericTableProps<T> {
columns: TableColumn<T>[]; // 컬럼 정보 배열
data: T[]; // 표시할 데이터 배열
}
// params를 제네릭으로 활용
function GenericTable<T>({ columns, data }: GenericTableProps<T>) {
return (
<table>
<thead>
<tr>
{columns.map((column) => (
<th key={String(column.key)}>{column.title}</th>
))}
</tr>
</thead>
<tbody>
{data.map((record, index) => (
<tr key={index}>
// 생략 ...
</tr>
))}
</tbody>
</table>
);
}
// 데이터
const users = [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 2, name: 'Jane Doe', age: 25 },
];
// 컬럼 정의
const columns = [
{ title: 'ID', key: 'id' },
{ title: 'Name', key: 'name' },
{ title: 'Age', key: 'age' },
];
function App() {
return <GenericTable columns={columns} data={users} />;
}
Typescript은 type을 쉽게 구성하기 위해 위에서 설명한 제네릭을 통해 lib.es5.d.ts에 미리 여러 유틸리티 타입을 만들어놨습니다. 자주 사용되는 몇몇 유틸리티 타입에 대해 설명 및 해당 타입이 어떻게 구현됐는지 정리해보겠습니다.
<T>
Partial<T>
타입의 모든 속성을 옵셔널으로 변경한 새로운 타입을 반환합니다.
// 구현된 방식
// T에 속한 모든 속성(P)에 ? 연산자를 통해 옵셔널로 변경
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 사용 예시
interface Person {
name: string;
nickname: string;
}
type PartialPerson = Partial<Person>;
const me: PartialPerson = { name: "안동원" } // 닉네임 없이 사용 가능
위 예시에 나와있는 선언된 PartialPerson은 아래 이미지와 같이 타입이 선언됩니다.
<T
>Required<T>
타입은 Partial<T>
와 반대로 모든 속성을 옵셔널에서 필수로 변경한 새로운 타입을 반환합니다.
// 구현된 방식
// T에 속한 모든 속성(P)에 -? 연산자를 통해 옵셔널 제거
type Required<T> = {
[P in keyof T]-?: T[P];
};
// 사용 예시
interface Developer {
name: string;
skill?: string;
}
type TDeveloper = Required<Developer>;
const developer: TDeveloper = { name: '안동원', skill: 'typescript' };
위 예시에 나와있는 TDeveloper은 아래 이미지와 같이 타입이 선언됩니다.
<T
>Readonly<T>
는 모든 속성을 읽기 전용으로 변경한 새로운 타입을 반환합니다.
// 구현된 방식
// T에 속한 모든 속성(P)에 readonly 키워드를 추가
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 사용 예시
type TNotice = {
id: number;
title: string;
content: string;
};
const notice: Readonly<TNotice> = {
id: 1,
title: 'title',
content: 'content',
};
// 읽기 전용이기 때문에 에러 발생
notice.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
<K,T
>Record<K,T>
는 제네릭의 key를 속성으로, 제네릭의 Type를 속성값을 타입으로 지정하는 새로운 타입을 반환합니다.
// 구현된 방식
// K의 속성을 새로운 타입의 속성이 되며, T는 타입이 됩니다.
type Record<K extends keyof any, T> = {
[P in K]: T;
};
// 사용 예시
type TPlatformType = 'android' | 'ios' | 'web';
type RecordPlatformType = Record<TPlatformType, string>;
// TPlatformType의 값이 속성 값이 됨
const platformType: RecordPlatformType = {
android: '안드로이드',
ios: '아이폰',
web: '웹',
};
Pick<T, K>
은 제네릭의 타입으로 부터 제네릭의 Key에 해당하는 속성을 선택하여 따로 모아 타입을 반환합니다.
// 구현된 방식
// K extends keyof T
// K는 타입 T의 필드만 가질 수 있는 합집합임을 의미합니다
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
// 사용 예시
interface IUser {
id: number;
name: string;
profile: string;
}
type TUserProfile = Pick<IUser, 'id' | 'profile'>;
const userProfile: TUserProfile = {
id: 1,
profile: 'profile',
};
Exclude<T,U>
는 Type1에서 Type2를 제외한 나머지 타입을 반환합니다.
// 구현된 방식
type Exclude<T, U> = T extends U ? never : T;
// 사용 예시
type TNoticeType = 'general' | 'important' | 'system' | 'event' | 'update';
type ExcludeNoticeType = Exclude<TNoticeType, 'update' | 'system'>;
const notice1: ExcludeNoticeType = 'general';
const notice2: ExcludeNoticeType = 'important';
const notice3: ExcludeNoticeType = 'event';
// const notice4: ExcludeNoticeType = 'update'; // 에러
예시로 나와있는 ExcludeNoticeType은 아래 이미지와 같이 선언됩니다.
Pick<T,K>
타입과 반대로 Omit<T,K>
타입은 제네릭 key에 해당하는 속성을 제외한 나머지를 따로 모아 새로운 타입을 반환합니다.
// 구현된 방식
// Pick과 Exclude를 활용하여 구현
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
// 사용 예시
interface IUser {
id: number;
name: string;
profile: string;
}
type TUser = Omit<IUser, 'profile'>;
const userProfile: TUser = {
id: 1,
name: '안동원',
};