본문 바로가기

Study/TypeScript

Generic

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 자동완성*/)

 

  1. generic 타입이란, 다른 타입과 연결되어 정의되는 타입을 말한다. 이 때 다른 타입이 어떤 타입이어야 하는지는 자유롭게 정의할 수 있다. 
  2. 예를 들어 배열 그 자체는 타입으로서의 의미가 없고, 배열울 구성하는 데이터의 타입(string, number, object...)과 함께  그 의미를 생성한다.
  3. 따라서 Array는 generic type 중 하나이다. 
  4. ts에게 배열의 아이템이 어떤 타입인지 명시하고 있으므로, 그 타입과 관련된 메서드를 자동완성으로 사용할 수 있게 된다. 
  5. 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!

 

  1. 일반적으로(자주) generic funtion에서 정의하는 generic type은, 그 함수의 매개 변수의 타입으로 사용된다.
  2. T, U | ts에게 정확히 어떤 타입을 인자로 받아올지 알 수 없다는 것을 명시한다. 이 때 T와 U는 고정된 타입이 아니라, 함수가 실행될 때 동적으로 결정되는 타입이다. 
  3. 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'}); // 무슨 일이 일어날까요?

 

  1. indexOf(item)는 배열에서 해당 아이템을 찾지 못하면 -1을 반환하는데, 이것은 배열의 가장 마지막 아이템을 가리키게 된다. 
  2. objStorage에 추가한 { name: 'harry' } 데이터와, 삭제한 { name: 'harry' } 데이터는 같아보이지만 다른 데이터이다. 자바스크립트에서 객체는 참조 타입이기 때문에, 서로 다른 메모리 주소에 저장되기 때문이다. 
  3. 따라서 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; 
}

 

  1. let courseGoal: Partial<CourseGoal> = {} | CourseGoal의 모든 프로퍼티를 optional 프로퍼티로 만든다. => courseGoal에 빠진 프로퍼티가 있다는 에러 메세지가 사라진다. 
  2.  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]
    }
}

 

  1. generic | data 배열에 string, number, boolean이 혼합될 수 없다. 
  2. 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