🍒 (230317 TIL) Understanding TypeScript
Generics 2️⃣
Keyof Constraint
제네릭으로 객체의 제약을 주는 경우, 키치가 필요한 경우 keyof
를 사용한 제약을 사용하면 실수를 방지할 수 있다.
function extractAndConvert<T extends object, U extends keyof T>(
obj: T,
key: U
) {
return 'Value: ' + obj(key);
}
extractAndConvert({}, 'name'); // Error occurred
제네릭 T
의 제약으로 object
설정하고,
U
의 제약으로 T
의 key
설정했습니다.
extractAndConvert
로빈 객체({}
)와 name
라는 문자열을 전달할 때,
빈 객체에 name
라는 키 값이 없기 때문에 컴파일 에러가 나온다!
제네릭으로 U extends keyof T
설정했기 때문에 발생하는 오류이며,
함수로서 name
키를 가진 객체를 전달하면 오류가 사라집니다 😃
Generic Classes
제네릭으로 클래스를 작성하는 경우, 새로운 인스턴스를 작성할 때에 유연하게 형태를 변경할 수가 있다.
class DataStorage<T> {
private data: T() = ();
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
getItems() {
return (...this.data);
}
}
const textStorage = new DataStorage<string>();
textStorage.addItem('Apeach');
textStorage.addItem('Rabbit');
textStorage.removeItem('Apeach');
console.log(textStorage.getItems()); // ('Rabbit')
위의 예에서 제네릭을 사용하지 않는 경우 class
직접 유형을 지정한 경우 string | number
등…
필요한 유형을 직접 추가해야하는 번거로움이 있었을 것입니다 🤔
선언 시점이 아닌 생성 시점에 필요한 유형을 지정할 수 있습니다.
라는 것은 제네릭의 강대한 장점!
물론 제약을 제네릭에 추가하는 방법도 있다 😅
Partial type
Partial 형식은 제네릭에 전달된 형식이 개체임을 알리는 역할을 하면서
건네받은 형태로 작성된 모든 속성을 옵션형으로 변경합니다.
interface CourseGoal {
title: string;
description: string;
completeUntil: Date;
}
function createCourseGoal(
title: string,
description: string,
date: Date
): CourseGoal {
let courseGoal: Partial<CourseGoal> = {};
courseGoal.title = title;
courseGoal.description = description;
courseGoal.completeUntil = date;
return courseGoal as CourseGoal;
}
Readonly type
Readonly
타입은 제네릭에게 전달된 타입이 무조건 읽기만 가능하다는 것을 알려주는 역할을 한다.
Readonly
타입을 사용하면 속성의 변경이나 추가가 불가능합니다 😵
const names: Readonly<string()> = ('Shou', 'Apeach');
names.push('Rabbit'); // Error occurred
names.pop(); // Error occurred
Utility Types
https://www.typescriptlang.org/docs/handbook/utility-types.html
Generics vs Union types
class DataStorage<T> {
private data: T() = ();
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data.splice(this.data.indexOf(item), 1);
}
getItems() {
return (...this.data);
}
}
const numberStorage = new DataStorage<number>();
제네릭의 경우 인스턴스를 만들 때 number
형태의 배열을 형태로 지정하면 지정할 수 있습니다만,
class DataStorage {
private data: (string | number | boolean)() = ();
addItem(item: string | number | boolean) {
this.data.push(item);
}
removeItem(item: string | number | boolean) {
this.data.splice(this.data.indexOf(item), 1);
}
getItems() {
return (...this.data);
}
}
union
유형은 data
라는 배열에 any
유형을 지정한 것과 다르지 않습니다 ..🫢 (예는 실제로 any
처럼 작동하지 않지만)
이 경우 data
유형 string() | number() | boolean()
로 설정할 수 있지만,
이렇게 되면 data
를 어떤 타입의 배열로 설정하는가에 따라 item
의 타입도 유동적으로 바뀌어야 하기 때문에 item
의 타입 설정이 어려워진다😡
제네릭은 특정 유형을 고정하거나 생성된 클래스 전체 인스턴스 전체에서 동일한 함수를 사용하거나 함수 전체에서 동일한 유형을 사용할 때 유용합니다.
union
유형은 모든 메소드 호출과 모든 함수 호출마다 다른 유형을 지정하려는 경우에 유용합니다.