타입스크립트 Section 7 : 제네릭

BRANDY·2023년 3월 14일
0

내장 제네릭 & 제네릭이란?

다른 타입과 연결되는 종류이며 다른 타입이 어떤 타입이어야 하는지에 대해서는 크게 상관하지 않는다.
제네릭 타입을 사용하는 경우 입력되는 데이터 타입을 지정해주어 타입을 결정할 수 있다. 예를 들어 배열은 그 자체로 타입이지만 배열에 전달되어야 하는 데이터 타입을 지정할 수 있다.

const names: Array<string> = []; // string[]과 같고, 데이터가 string 타입 이라는 의미

제네릭 함수 생성하기

함수의 매개변수와 반환 타입에 제네릭 타입 T, U를 사용하여 타입스크립트가 입력되는 데이터 타입을 추론하여 타입을 함수 호출을 위해 지정한다.

function merge(objA: object, objB: object) {
    return Object.assign(objA, objB);
}

console.log(merge({name: 'Max'}, {age: 30});
// {name: 'Max', age: 30}

//T, U를 입력하여 타입스크립트가 타입을 추론하게 한다. 어떠한 타입이 입력될지 모른다는 의미를 전달.
function merge<T, U>(objA: T, objB: U) {
    return Object.assign(objA, objB);
}
const mergedObj = merge({name: 'Max'}, {age: 30}) 
console.log(mergedObj.age)

제약 조건 작업하기

extends를 작성하여 타입에 대한 특정 제약 조건을 설정할 수 있다. 이는 즉 원하는 타입을 지정해줄 수 있다.

function merge<T extends object, U extends object>(objA: T, objB: U) {
    return Object.assign(objA, objB);
}

const mergedObj = merge({name: 'Max'}, {age: 30}) 
console.log(mergedObj.name)

다른 일반 함수

interface Lengthy {
    length: number;
}

function countAndDescribe<T extends Lengthy>(element: T): [T, string] {
    let descriptionText = 'Got no value.';
    if(element.length === 1) descriptionText = 'Got 1 element.';
    else if(element.length > 1) dedcriptionText = 'Got' + element.length + 'elements.';
    return [element, descriptionText];
}

length를 명확하게 명시하기 위해 인터페이스를 사용, T extends Lengthy라는 제약조건을 설정하여 무엇이든 length 속성이 반환되며 배열이나 문자열은 length 속성을 지닌다는 것을 알려준다.

keyof 제약 조건

function extractAndConvert(obj: object, key: string) {
    return obj[key];
}

입력한 객체가 무엇이든 이 키를 가지는지 알 수 없기 때문에

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

extractAndConvert({ name: 'Max' }, 'name');

extends keyof T 키워드를 사용하여 정확한 구조를 갖고자 한다는 것을 타입스크립트에 알려준다.

만일 함수의 매개변수 key가 반드시 매개변수 obj의 제네릭 타입 T(객체를 받게되는)에 존재하여야 할때, keyof T 를 하면 객체의 key 값을 모아 유니온 타입으로 만들 수 있다.

typeof

객체 형태의 타입을 따로 속성들만 뽑아 모아 유니온 타입으로 만들어주는 연산자

제네릭 클래스

타입스크립트에 추가적인 정보를 입력하여 하나 이상의 제네렉 타입을 입력할 수 있다. 또한 클래스 내에 자체 제네릭 유형을 지닌 메소드를 입력해도 된다.

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('Max'); 
textStorage.addItem('Manu'); 
textStorage.removeItem('Max');
console.log(textStorage.getItems()); //['Manu']

제네릭 유틸리티 타입

partial

타입을 Optional 타입으로 바꿔주는 타입, 특정 타입의 부분 집합을 만족하는 타입을 정의할 수 있다.

interface MyUser { //기존 코드
    name: string;
    id: string;
    email?: string;
    phone?: string;
}

interface MyUser { //partial을 적용하여 모든 요소를 옵셔널 타입으로 생성
    name: string;
    id: string;
    email?: string;
    phone?: string;
}

type MyUserOptionals = Partial<MyUser>

위와같이 설정하면 모든 요소를 옵셔널로 지정한 타입으로 생성할 수 있다. 즉 전달받은 타입의 모든 하위 집합을 나타내는 타입을 생성할수 있다.

Readonly

속성을 변경하거나 이 객체에 새 속성을 추가할 수 없는 읽기만 가능한 타입

const names: Readonly<string[]> = ['Max', 'Anna'];
names.push('Manu'); // 추가는 불가.

제네릭 타입 vs 유니언 타입

유니언 타입은 함수를 호출할 때마다 타입들 중 하나로 호출할 수 있는 함수가 필요한 경우에 유용하다. 즉 모든 메서드 호출이나 모든 함수 호출마다 다른 타입을 지정하고자 함에 사용하자.

class DataStorage {
    private data: (string | number | boolean)[] = [];
    addItem(item: string | number | boolean) {
        this.data.push(item);
    }
}

제네릭 타입은 특정 타입을 고정하거나 전체 클래스 인스턴스에 걸쳐 같은 함수를 사용하거나, 전체 함수에 걸쳐 같은 타입을 사용하고자 할때 유용하다. 즉 타입을 고정하여 해당하는 타입만이 필요할때 사용하자.

class DataStorage<T> {
    private data: T[] = [];
    
    addItem(item: T) {
        this.data.push(item);
    }
}
profile
프런트엔드 개발자

0개의 댓글