타입스크립트 정리 10: 제네릭

Kimhojin_Zeno·2023년 5월 21일
0

타입스크립트 정리

목록 보기
10/13

매우 중요

그런데 구문이 좀 투박하다. 못 생김.

이해하기 살짝 까다롭다.

Generics

function identity<T>(item: T): T {  // T는 Type이라는 뜻
	return item;
}

제네릭(Generics)이란 TypeScript에서 여러 타입에서 사용할 수 있는 재사용 함수나 재사용 클래스를 정의할 수 있게 해주는 특수 기능 또는 특수 구문이다.

sting을 넣으면 string이 나오고, number를 넣으면 number가 나온다는 것을 타입스크립트가 예상할 수 있게 만들어준다.

함수를 호출할 때 함수 안에서 사용할 타입을 건네줄 수 있다.

인풋의 타입을 <> 홑화살괄호 안에 입력해서 반환되는 타입을 제어한다

제네릭 구문을 쓰면 단순히 애너테이션으로 타입을 정해줄때보다 코드를 더 적게 쓸수 있다.

예를들어 string Number 두개의 함수를 써야할것을 제네릭을 쓰면 하나로 쓸 수 있음.

Generic 함수 예시

function getRandomElement<T>(list: T[]): T {
	const randIdx = Math.floor(Math.random() * list.length);
	return list[randIdx];
}

console.log(getRandomElement<string>(["a", "b", "c"]));
getRandomElement<number>([5,6,4,3,1]);

특정 타입이 들어있는 배열을 받아서 랜덤하게 요소 하나를 리턴하는 함수.

이걸 string, number, boolean 버전으로 3개 만들 필요 없이,

제네릭 구문을 이용해서 하나만 만들고, 사용할때 홑화살표 안에 string, number 등을 넣어주면

타입스크립트가 타입을 예측할 수 있음.

여러 타입을 가진 제네릭

function merge(object1, object2) {
	return {
		...object1,
		...object2
	}
}

merge({ name: "colt" }, { pets: ["blue", "elton"] });

타입을 지정해주지 않으면 object1 과 object2는 any이고 밑의 예시도 any이다.

object1과 object2의 객체 속 요소들이 타입이 전부 다르다면

타입을 지정해 주려면 어떻게 해야할까?

function merge<T, U>(object1: T, object2: U) {
	return {
		...object1,
		...object2,
	};
}

const comboObj = merge({ name: "colt" }, { pets: ["blue", "elton"] });

이렇게 T 다음 U로 타입 파라미터를 준다. for loop에서 i 다음 j를 주는 것과 마찬가지 관례.

이러면 타입스크립트는 예시에서

첫번째 객체 값이 string, 두번째 객체 값이 string[] 이라는 것을 예측할 수 있게 된다.

따라서 merge하면 그 객체의 값은 string & string[] 이 나온다고 예측한다.

타입 제한 추가하기

위 함수에서 다음과 같이 넣으면 어떻게 될까?

merge({ name: "Colt" }, 9)

숫자는 스프레드 구문 …9 해봐야 아무것도 반환하지 않는다. 원하는 결과가 아니다.

이를 피하기 위해 타입에 제한을 걸어두는 기능을 사용한다.

function merge<T extends object, U extends object>(object1: T, object2: U) {
	return {
		...object1,
		...object2,
	};
}

const comboObj = merge({ name: "colt" }, { pets: ["blue", "elton"] });

extends를 사용해 객체 타입으로 확장하는 것이다.

항상 객체여야 하는 것이다.

객체의 타입(문자열로 된 객체인지, 불리언으로 된 객체인지는) 중요하지 않음. 객체이기만 하면 된다는 뜻이다.

이 상태에서

merge({ name: "Colt" }, 9)

이렇게 넣으면 에러가 난다. 숫자는 객체가 아니기 때문.

interface Lengthy {
		length: number;
}

function printDoubleLength<T extends Lengthy>(thing: T): number {
	return thing.length * 2;
}

printDoubleLength("aefew") // 10
printDoubleLength(235) // 에러가 난다. length가 없기 때문.

function printDoubleLength(thing: Lengthy): number { // 제네릭 사용하지 않고 같은 기능
	return thing.length * 2;
}

exends 를 없애버리면 에러가 난다. 어떤 타입일지 모를 T에 length 프로퍼티가 있을지 없을지 모르기 때문.

하지만 미리 정해둔 인터페이스 Lengthy로 한정하면 ‘number인 length가 반드시 있는’ 타입이라는 뜻이 되므로 에러가 나지 않는다.

기본 타입 파라미터

function makeEmptyArray<T = number>(): T[] {
	return [];
}

const strings = makeEmptyArray(); // const strings: number[]

const  boo = makeEmptyArray<boolean>(); // const boo: boolean[]

홑화살표 타입에 등호를 넣어 = 타입을 미리 넣어주면

기본값을 가지게 된다.

그 다음부터 아무런 타입 파라미터를 주지 않아도 기본으로 설정해둔 타입이 적용된다.

물론 기본값고 다른 타입을 홑화살표에 넣어서 사용하면 기본값이 아닌 해당 타입으로 적용된다.

기본값은 타입 파라미터를 특정하지 않을때만 개입.

제네릭 클래스 작성

interface Song {
	title: string;
	artist: string;
}

interface Video {
	title: string;
	creator: string;
	resolution: string;
}

class Playlist<T> {
	public queue: T[] = [];
	add(el: T) {
		this.queue.push(el)
	}
}

const songs = new playlist<Song>()
songs.add(   // Song을 넣어야 한다고 뜬다.

const videos = new playlist<Video>()
videos.add(  // Video를 넣어야 한다고 뜬다.

이렇게 클래스에 포함되는 다양한 메서드를 작성할 때 타입을 구성하게 제네릭을 사용할 수 있다.

요소를 없애거나 순서를 바꾸거나 할때 타입 메서드면 큰 문제가 생기지 않는다.

profile
Developer

0개의 댓글