TypeScript Generics

강정우·2023년 6월 16일
0

TypeScript

목록 보기
20/23
post-thumbnail

generics

  • 오직 ts에만 있고 js에는 없는 개념이다.
  • 2가지 이상의 타입으로 연결되어 지정되며 명시된 타입끼리 상호 유기적인 작용을 한다. 무슨뜻이냐? =>
const names: Array<string> = [];
const names2: string[] = [];

names[0].split(' ');
names2.split(' ');		// => 에러
  • names는 아직 값이 지정되지 않았음에도 불구하고 split 메서드가 이상없이 돌아간다는 뜻이다.

promise 타입

  • 만약 http req를 갖다오는 promise 타입이 있다고 가정해보자. 그럼 이제 promise 결과값에 대해서 어떠한 정보도 알 수 없어 다음 로직을 작성할 수 없다.

  • 하지만 여기에 명시를 해준다면

  • 해당 결과값에 대한 타입을 알 수 있고 그렇다면 다음 로직을 이어서 작성해나갈 수 있다.

T, U, V...

  • 주로 T가 type의 대표적으로 쓰이고 다음에 올 타입을은 알파벳 순서대로 작성된다. 여기서 T를 extends로 쓴 이유는 =>
    The fact that we get the error for the first argument only, is a consequence of the Object.assign definition in native JS
    대충 바닐라 JS의 assign 함수의 정의가 바뀌었기 때문에 첫번째 변수에도 사용할 수 있도록 제약조건을 걸어줘야한다.
function merge<T extends object, U>(objA:T, objB:U) {
    return Object.assign(objA, objB);
}

const mergeObj = merge({name: '헬창', hobbies: ['개발']}, 30);
  • TS가 각각의 타입을 추론하여 매우 유연하게 로직을 설정할 수 있다.
    또한 위 textends는 type constraints과도 연관이 있다.

type constraints (=extends)

  • extends 키워드로 T 타입이 어느 타입이 와도 되지만 반드시 object를 상속받는 타입이어야한다고 제약조건을 주는 것이다.

  • 그래서 위 코드는 컴파일 과정에서 이상없이 작동하지만 분명 의도한 바와는 다르게 작동할 것이다.
    따라서 extends로 제약을 주는 것이다.

generic function

  • 위에 함수 제너릭에서 T를 쓰고자 할 때 .length라는 속성값은 string과 array에는 분명히 있지만 현재 TS는 알 수 없다. 그래서 length 속성이 있다는 것을 알고 있는 우리가 직접 type을 만들어서 이를 상속받게 하는 것이다.
type Length = {
    length: number;
}
function countAndDescribe<T extends Length>(element:T):[T, string] {
    let descriptionText = 'Got no value.';
    if(element.length===1){
        descriptionText = 'Get 1 element';
    } else if(element.length > 1) {
        descriptionText = 'got' + element.length +'elements';
    }
    return [element, descriptionText]
}

console.log(countAndDescribe(['hi there', 'hello']));
  • 그렇다면 그냥 length 속성값이 존재하는 string을 상속받으면 되지 왜? 굳이 귀찮게 type을 만들어서 상속받아야 하는가? => 입력값으로부터 자유로울 수 없기 때문이다.

  • 참고로 어러개를 상속받을 수 있다.
type Length = {
    length: number;
}
function countAndDescribe<T extends Length|string|number|boolean>(element:T):[T, string] {
  ...

keyof

  • 만약 우리가 obj와 그에 해당하는 키값을 받아서 obj의 key에 대한 value를 뽑아내고 싶을 때 사용한다.
    하지만 아래와 같이 사용하면 TS는 argument로 받은 key가 obj에 존재하는지 전혀 모른다.

  • 그래서 존재하는 키워드가 바로 keyof이다. 이름 그대로 꽤나 직관적인 기능을 갖고있다.

function extractAndConvert<T extends object, U extends keyof T>(obj:T, key:U) {
    return 'Value: ' + obj[key];
}

extractAndConvert({name:'정우'}, 'name');

OBJ 타입 다룰 때 주의사항

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 objStorage = new DataStorage<object>();
objStorage.addItem({name: '헬창'})
objStorage.addItem({name: '개발자'})
objStorage.removeItem({name: '헬창'})
console.log(objStorage.getItems());

  • 분명히 코드에서 헬창을 지웠는데 헬창만 남은 상황이다. 왜일까?

  • 문제는 바로 this.data.indexOf가 같은 메모리를 참조하지 않다. 왜? => objStoreage의 addItem의 객체와 removeItem의 객체가 서로 다른 객체이기 때문이다.
    그래서 같은 메모리를 참조하는 객체를 찾지 못 하고 -1을 반환하기 때문에 제일 뒤에서 첫번째 배열을 지운 것이다.

  • 이는 저렇게 객체를 따로 선언할 것이 아니라 어떠한 변수 하나에 담아서 해당 변수를 돌려써야한다는 것이다.

Partial<custom obj>

  • 쉽게 말하면 generic type으로 들어간 커스텀 obj 데이터의 속성값을 모두 ? 로 바꿔주는 utility 제네릭 타입이다.
type 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;
}
  • 이는 아직 존재하지 않는 데이터를 처리할 때 매우 유용하다. 그리고 return 값또한 사용자가 해당 generic 값을 다 채워서 return할 것을 할기에 as 키워드로 변환해서 반환하면 된다.

Readonly

  • 또한 obj의 속성뿐만 아니라 타입에도 Readonly를 붙여줄 수 있다.

Generic type VS Union type

  • 둘이 굉장히 헷갈릴 수 있는데 쉽게 말하면 Generic type을 여러개 설정한다면 그중 한개를 선택하겠다는 것이고 union 타입은 여러개를 설정한다면 그것들을 모두 받아들이겠다는 뜻이다.

ref

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글