<T>
,<K>
,<U>
처럼 사용하며 타입스크립트에서 중복되는 코드를 효과적으로 줄이고 고급 문법을 작성할 수 있게 해주는 제네릭에 대해 알아보겠습니다.
제네릭은 타입을 미리 정의하지 않고 사용하는 시점에 원하는 타입을 정의해서 쓸 수 있는 문법입니다. 마치 함수의 파리미터와 같은 역할을 합니다.
function getText(text) {
return text;
}
getText()
는 text
를 받아서 그대로 반환해줍니다. 문자열이 들어오거나, 정수가 들어와도 그대로 반환합니다. 이 원리를 타입스크립트에 대입해 '타입을 넘기고 그 타입을 그대로 반환받는다' 가 제네릭의 원리입니다.
function getText<T>(text: T): T {
return text;
}
제네릭의 기본 문법입니다. 이렇게 하면 getText()
를 호출할 때 아무 타입이나 넘길 수 있습니다.
getText<string>('hi')
와 같이 호출 할 수 있습니다.
이렇게 호출하면 getText()
는 마치
function getText(text: string): string {
return text;
}
라고 선언된 것과 같습니다. getText<number>(10)
이렇게 호출하면 모든 타입이 number
로 지정된것과 같습니다.
function getText(text: string): string {
return text;
}
function getNumber(num: number): number {
return num;
}
function getBool(bool: boolean): boolean {
return bool;
}
function getArr(arr: []): [] {
return arr;
}
function getObj(obj: {}): {} {
return obj;
}
위의 함수들은 인자로 받아온 값을 그대로 넘겨준다는 것에 있어서 똑같은 코드입니다. 다른점은 파라미터와 반환값의 타입입니다.
이러한 코드들을 DRY(Don't Repeat Yourself) 라는 개발 원칙이 있습니다.
이런 상황에 제네릭을 사용하면
function getSomething<T>(something: T): T {
return something;
}
반복되는 5개의 함수를 하나로 줄였습니다.
any
를 사용해도 됩니다. 하지만 any
는 마치 자바스크립트처럼 모든 타입을 취급하는 대신, 타입스크립트의 코드 가이드나 에러방지 혜택을 받지 못하기에 좋은 방법은 아닙니다.
interface ProductDropdown {
value: string;
selected: boolean;
}
interface StockDropdown {
value: number;
selected: boolean;
}
상품목록과 생품재고를 보여주는 드랍다운 UI를 인터페이스로 정의한 코드입니다.
여기서 value
에 다른 데이터 타입을 갖는 드랍다운이 필요하면 어떻게 해야될까요?
interface AddressDropdown {
value: { city: string; zipCode: string };
selected: boolean;
}
이렇게 추가적으로 새로운 인터페이스를 정의해 주어야 할 것입니다.
이런식으로 모든 데이터 타입을 하나하나 정의 해주면 타입 코드가 많아져 관리도 어렵고 번거로운 작업이 됩니다. 이때 제네릭을 사용할 수 있습니다.
interface Dropdown<T> {
value: T;
selected: boolean;
}
인터페이스 이름 오른쪽에 <T>
를 붙이고, 인터페이스 속성 중 제네릭으로 받은 타입을 사용할 곳에 T
타입을 연결해줍니다. 이렇게 하면 타입을 유연하게 확장이 가능하고,
위에 나왔던 ProductDropdown
, StockDropdown
, AddressDropdown
인터페이스를 하나의 인터페이스로 정의가 가능합니다.
let product: ProductDropdown;
let stock: StockDropdown;
let address: AddressDropdown;
let product: Dropdown<string>;
let stock: Dropdown<number>;
let address: Dropdown<{ city: string; zipCode: string }>;
이렇게 Dropdown
인터페이스를 하나만 만들고 제네릭을 사용해 유연하게 타입을 처리할 수 있습니다.
function getSomething<T>(something: T) : T {
return something;
}
이 함수는 제네릭을 선언했기 때문에 어떤 타입이든 받을 수 있습니다.
만약 문자열만 받는 제네릭을 선언하고 싶다면 extends
를 사용해 제약할 수 있습니다.
function getSomething<T extends string>(something: T) : T {
return something;
}
<T>
에 extends 타입
을 적어주면 그 타입만 사용할 수 있습니다.
일반적으로 타입을 제약할 때는 여러개의 타입중 몇개만 쓸 수 있게 제약합니다.
function lengthOnly<T extends { length: number }>(value: T) {
return value.length;
}
이렇게 하면 length
속성을 갖는 타입만 취급하도록 제약합니다. 이렇게 된다면 받을 수 있는 타입은 string
, array
,object
입니다. 이 세개의 타입이 length
속성을 가지고 있기 때문입니다.
keyof
는 특정 타입의 키 값을 추출해서 문자열 유니언 타입으로 변환해줍니다.
Developer
타입에 마우스 커서를 올려보면 객체의 키가 유니언 타입으로 변환되어 있는걸 알 수 있습니다. 만약 속성이 3개라면 "name" | "skill" | "속성3"
형태의 유니언 타입이 반환되었을 겁니다.
function printKeys<T extends keyof { name: string; skill: string; }> (value: T) {
console.log(value);
}
위 코드는 객체의 키 값만 인자로 받아 출력하도록 제네릭의 타입제약을 걸어놓은 함수입니다.
extends
와keyof
를 사용해 name
, skill
속성을 갖는 객체의 키만 타입으로 받겠다고 정의했습니다. 이 함수의 제네릭은 파라미터인 value
에 연결 되어있기 때문에 함수를 호출할 때 넘길 수 있는 인자는 문자열 name
과 skill
입니다. 다른 값을 넘기면 에러가 발생합니다.
이처럼 문자열 name
, skill
이 아닌 겨웅에는 에러가 발생합니다.
이처럼 extends
와 keyof
를 사용해 타입의 제약을 까다롭게 만들 수 있습니다.
타입스크립트의 제네릭에 대해서 정리해봤습니다.
불필요한 타입 코드를 줄이고, 타입스크립트의 이점인 코드 가이드도 활용할 수 있습니다.
extends
와 keyof
내용은 헷갈리지만 제네릭은 타입스크립트의 중요한 개념이니 까먹지 않도록 하겠습니다.