1. 개요
다양한 타입이 들어올 수 있도록 유연성을 제공하는 문법.
예를 들어 다음과 같이 작성한다.
function identity<T>(arg: T): T {
return arg;
}
let output1 = identity('txt'); // type: string
let output2 = identity(12345); // type: number
let output3 = identity(true); // type: boolean
class ItemStorage<T> {
private storage: T[] = [];
add(item: T) {
this.storage.push(item);
}
remove(item: T) {
this.storage.filter((value) => item !== value);
}
show() {
return [...this.storage];
}
}
const stringStorage = new ItemStorage<string>();
2. 상세
2.1. union type과의 차이점
generics
의 특성은 다음과 같다.
- 여러 타입을 허용하되, 사용시점에 구체적인 타입이 지정된다.
- 한 개의 함수에 다양한 타입을 사용할 수 있으므로 재사용성이 증가한다.
- 사용시점에 정해진 타입을 그대로 유지한다.
union type
의 특성은 다음과 같다.
- 제시된 타입들 중 한 개를 선택할 수 있다.
union type
에 포함된 타입들의 공통 속성만 접근 가능하다.
generic
과 union type
을 상호배타적 관계로 생각하면 안 된다.
둘은 각각의 목적을 가지며, 둘을 함께 조합해서 사용할 수도 있다.
예시를 살펴보자.
아래 코드는 generic
으로 들어올 수 있는 타입을 extends
키워드를 이용하여 number
와 string
으로 제한하였다.
따라서 boolean
타입의 인자를 넣었을 때 에러가 발생한다.
function identity<T extends number | string>(arg: T): T {
return arg;
}
let output1 = identity('sample'); // type: string
let output2 = identity(12345678); // type: number
let output3 = identity(true); // Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
2.2. keyof 제약
keyof로도 타입을 제약할 수 있다.
객체와 객체의 key를 인자로 넣으면, 해당 key에 대한 value를 반환하는 함수가 있다고 가정해보자.
const sampleObject = {
name: 'YU',
address: 'Seoul'
};
const getProperty = (obj: object, key: string) => {
return obj[key];
};
getProperty(sampleObject, "name"); // "YU"
getProperty(sampleObject, "age"); // undefined
이 함수에서 발생할 수 있는 문제점은, 객체에 정의되어있지 않은 key를 인자로 넣어도 에러가 발생하지 않는다는 것이다.
이 문제는 keyof
와 generic
을 조합하여 해결할 수 있다.
const sampleObject = {
name: 'YU',
address: 'Seoul'
};
const getProperty = <T extends object, U extends keyof T>(obj: T, key: U) => {
return obj[key];
};
getProperty(sampleObject, "name"); // "YU"
getProperty(sampleObject, "age"); // Argument of type '"age"' is not assignable to parameter of type '"name" | "address"'.
generic
은 사용시점에서 구체적인 타입이 지정되므로, 매개변수 key의 타입은 인자로 들어온 객체의 모든 key값이 되며, 이에 따라 타입 검사도 함께 이루어져 올바른 인자를 넣을 수 있게 된다.
3. utility type
Partial
T의 모든 속성을 선택적으로 만든다.
Partial<T>
Readonly
T의 모든 속성을 읽기 전용으로 만든다.
Readonly<T>
Required
T의 모든 속성을 필수로 만든다.
Required<T>
Pick
T에서 K 속성만 선택한다.
Pick<T, K>
Omit
T에서 K 속성을 제외한 나머지를 선택한다.
Omit<T, K>
Record
키 타입 K와 값 타입 T를 가진 객체 타입을 생성한다.
Record<K, T>
예시는 다음과 같다.
type Fruit = "apple" | "orange" | "banana";
type FruitCount = Record<Fruit, number>;
const data: FruitCount = {
apple: 1,
banana: 2,
orange: 3,
};
interface Person {
name: string;
age: number;
}
type Team = "HR" | "Accounting" | "dev";
type TeamRoster = Record<Team, Partial<Person>>;
const teams: TeamRoster = {
Accounting: { name: "kim" },
dev: { name: "lee", age: 27 },
HR: { age: 20 },
};