what is generic?
<Generics> In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is
generics, that is, being able to create a component that can work over a variety of types rather than a single one. This allows users to consume these components and use their own types.
// 1. compile error!
const names: Array = [];
// Generic type 'Array<T>' requires 1 type argument(s).
// 2. no error! (아래 두 가지 표현은 100% 동일함)
const names: Array<string> = ['harry', 'mary'];
const names: string[] = ['harry', 'mary'];
// 3. promise
// const promiseExample: Promise<unknown> => 기본값으로 Promise result의 타입이 unknown으로 지정된다
const promiseExample = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('300ms이 지났습니다');
}, 300);
})
// PromiseResult가 반환하는 값이 string 이라고 지정할 수 있다
const promiseExample: Promise<string> = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('300ms이 지났습니다');
}, 300);
})
promise.then(data => /*typeof data === string => string method 자동완성*/)
- generic 타입이란, 다른 타입과 연결되어 정의되는 타입을 말한다. 이 때 다른 타입이 어떤 타입이어야 하는지는 자유롭게 정의할 수 있다.
- 예를 들어 배열 그 자체는 타입으로서의 의미가 없고, 배열울 구성하는 데이터의 타입(string, number, object...)과 함께 그 의미를 생성한다.
- 따라서 Array는 generic type 중 하나이다.
- ts에게 배열의 아이템이 어떤 타입인지 명시하고 있으므로, 그 타입과 관련된 메서드를 자동완성으로 사용할 수 있게 된다.
- Promise 객체도 PromiseResult(fullfileld, rejected 일 때 각각 전달되는 값)의 타입과 연결되어 정의되는 generic 타입 중 하나이다.
how to create our own generic?
1. generic function
// 1.
// function merge(objectA: {}, objectB: {}): {}
function merge(objectA: {}, objectB: {}) {
return Object.assign(objectA, objectB)
}
const merged = merge({ name: 'harry' }, { age: 30 });
console.log(merged.name); //error! Property 'name' does not exist on type '{}'
// 2.
// function merge<T, U>(objectA: T, objectB: U): T & U => it returns intersection type
function merge<T, U>(objectA: T, objectB: U) {
return Object.assign(objectA, objectB)
}
const merged = merge({ name: 'harry' }, { age: 30 });
console.log(merged.name); // no error!
const merged2 = merge({ name: 'harry' }, 30); // no error!
// 3. Type Constraints
function merge<T extends object, U extends object>(objectA: T, objectB: U) {
return Object.assign(objectA, objectB)
}
const merged2 = merge({ name: 'harry' }, 30); // compile error!
- 일반적으로(자주) generic funtion에서 정의하는 generic type은, 그 함수의 매개 변수의 타입으로 사용된다.
- T, U | ts에게 정확히 어떤 타입을 인자로 받아올지 알 수 없다는 것을 명시한다. 이 때 T와 U는 고정된 타입이 아니라, 함수가 실행될 때 동적으로 결정되는 타입이다.
- Type Constraints | merge 함수가 하는 일을 살펴보았을 때, T와 U는 객체여야 한다(어떤 프로퍼티를 가지고 있어야 하는지는 구체적으로 제약하지 않지만). => Type Constraints(타입 제약)이 필요하다.
- 함수가 하는 일이 무엇인지에 따라, 타입 제약을 적절하게 사용할 수 있다.
2. generic class
// < T extends string | number | boolean >
class DataStorage<T> {
data: T[] = [];
addData(item: T) {
this.data.push(item);
}
removeData(item: T) {
this.data.splice(this.data.indexOf(item), 1)
}
getData() {
return [...this.data]
}
}
const testStorage = new DataStorage<string>();
testStorage.addData('hello'); // no error!
testStorage.addData(10); // error!
const objStorage = new DataStorage<object>();
objStorage.addData({name: 'harry'});
objStorage.addData({name: 'mary'});
objStorage.removeData({name: 'harry'}); // 무슨 일이 일어날까요?
- indexOf(item)는 배열에서 해당 아이템을 찾지 못하면 -1을 반환하는데, 이것은 배열의 가장 마지막 아이템을 가리키게 된다.
- objStorage에 추가한 { name: 'harry' } 데이터와, 삭제한 { name: 'harry' } 데이터는 같아보이지만 다른 데이터이다. 자바스크립트에서 객체는 참조 타입이기 때문에, 서로 다른 메모리 주소에 저장되기 때문이다.
- 따라서 removeData를 실행할 때 indexOf는 -1을 반환하고, 가장 마지막 아이템인 { name: mary }를 삭제할 것이다.
- DataStorage가 primitive 데이터에 대해서만 처리되도록 타입을 제한하는 것이 더 나은 방법일 수 있다.
+) keyof
// 1.
function extract(obj: object, key: string) {
return obj[key]; // compile error! => object에 key 속성이 있다고 보장할 수 없음
}
// 2.
function extract<T extends object, U extends keyof T>(obj: T, key: U) {
return obj[key]; // no error!
}
utility types
1. Partial
interface CourseGoal {
course: string;
price: number;
deadline: Date;
}
// 1.
function createCourseGoal(course: string, price: number, deadline: Date): CourseGoal {
// return { course, price, deadline }
let courseGoal:CourseGoal = {}; // error!
// Type '{}' is missing the following properties from type 'CourseGoal': course, price, deadline
courseGoal.course = course;
courseGoal.price = price;
courseGoal.deadline = deadline;
return courseGoal
}
// 2. Partial
function createCourseGoal(course: string, price: number, deadline: Date): CourseGoal {
let courseGoal:Partial<CourseGoal> = {}; // no error!
courseGoal.course = course;
courseGoal.price = price;
courseGoal.deadline = deadline;
// return courseGoal; but error here!
return courseGoal as CourseGoal;
}
- let courseGoal: Partial<CourseGoal> = {} | CourseGoal의 모든 프로퍼티를 optional 프로퍼티로 만든다. => courseGoal에 빠진 프로퍼티가 있다는 에러 메세지가 사라진다.
- courseGoal을 그대로 반환하려고 하면 에러가 발생하는데, 함수의 반환값 타입인 CourseGoal 타입이 아니라 Partial<CourseGoal> 타입을 반환하기 때문이다.
2. Readonly
const names: Readonly<string[]> = ['Harry', 'Anna'];
names.push('Olile'); // error!
- 배열 뿐만 아니라 객체를 Readonly로 설정해서, 프로퍼티를 추가하거나 수정할 수 없도록 만들 수도 있다.
generic vs. union
// 1. generic
class DataStorage<T extends (string | number | boolean)> {
private data: T[] = [];
addData(item: T) {
this.data.push(item);
}
removeData(item: T) {
this.data.splice(this.data.indexOf(item), 1)
}
getData() {
return [...this.data]
}
}
// 2. union
class DataStorage {
private data: (string | number | boolean)[] = [];
addData(item: string | number | boolean) {
this.data.push(item);
}
removeData(item: string | number | boolean) {
this.data.splice(this.data.indexOf(item), 1)
}
getData() {
return [...this.data]
}
}
- generic | data 배열에 string, number, boolean이 혼합될 수 없다.
- union | data 배열에 string, number, boolean이 혼합될 수 있다.
'Study > TypeScript' 카테고리의 다른 글
Module (0) | 2022.08.10 |
---|---|
Decorators (0) | 2022.08.03 |
Advanced Types (0) | 2022.07.23 |
Interface (0) | 2022.07.21 |
Class (0) | 2022.07.21 |