ts 핸드북 : (https://typescript-kr.github.io/pages/generics.html)
한눈에 보는 타입스크립트 : (https://heropy.blog/2020/01/27/typescript/)
generic에 대해 잘 설명한 포스트 : (infoscis.github.io/2017/05/25/TypeScript-handbook-generic/)
Generic 사용법
사실 Generic은 java와 같은 다른 언어에서 이미 사용되어 온 특징이다.
Generic은 재사용을 목적으로 함수나 클래스의 선언 시점이 아닌, 사용(호출) 시점에 타입을 선언할 수 있게 됩니다.
제네릭을 사용하게 되면 다음의 장점을 누릴 수 있습니다.
(1) 재사용하므로 타입 정의 코드를 줄여나갈 수 있다.
(2) 재사용을 위해 any 범벅을 만들지 않아도 된다. any는 헬퍼의 도움을 받을 수 없지만 제네릭은 타입을 부여해주므로 헬퍼가 작동하므로 generic을 쓰는 것이 훨씬 좋다.
// generic사용. 재사용 가능하면 타이핑도 들어가 자동 완성 해 줌
function logText<T>(param: T): T {
return param;
}
// any 사용. 재사용은 할 수 있으나 타이핑이 안 됨
function logText(param: any): any {
return param;
}
꼭 함수뿐만 아니라 인터페이스, 클래스, 함수, 타입 별칭 등에 사용할 수 있습니다.
<> 안의 문자는 꼭 T가 아니어도 됩니다. Type의 줄임말로 관습적으로 T를 사용할 뿐입니다.
// 함수에서 사용
function identity<T>(arg: T): T {
return arg;
}
// interface에서 사용
interface IValue<T> {
value: T;
}
// 클래스에서 사용
class Valuable<T> {
constructor(public value: T) {}
}
generic에 호출 시점에서의 타입 명시
함수를 정의하는 부분에서는 <T> 제네릭을 통해 타입을 선언하지 않았지만 <> 내에 특정 타입을 지정함으로써 타입을 지정할 수 있습니다. 보통 이 방식을 많이 사용하죠. 물론 지정하지 않아도 타입을 추론해서 적절한 제레닉 타입을 찾아냅니다.
function identity<T>(arg: T): T {
return arg;
}
// 타입 지정
console.log(identity<number>(3));
console.log(identity<string>("coding"));
// 타입 추론 해 줌
console.log(identity([1, 3, 5]));
리턴하는 타입이 명시된다는 것이 왜 중요하냐면, return된 값으로 활용할 때 헬퍼가 작동하기 때문입니다.
function logText<T>(text: T): T {
console.log(text);
return text;
}
const sttt = logText<string>("hello"); // string이구나
console.log(sttt.split("")); // string이므로 split 메서드 헬퍼가 작동함
interface, class에 generic 사용
클래스나 인터페이스도 마찬가지로 제네릭을 활용할 수 있으므로 다음과 같이 작성할 수 있습니다.
interface DropDown<T> {
value: T;
selected: boolean;
}
const obj: DropDown<number> = {
value: 1,
selected: false,
};
class Human<T, K> {
constructor(private _name: T, public age: K) {}
say(): void {
console.log(`${this._name} is ${this.age} years old`);
}
}
const me = new Human<string, number>("foo", 100);
console.log(me);
실제 제네릭 사용 예시
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<h1>이메일 dropdown</h1>
<select name="email-dropdown" id="email-dropdown">
<!-- <option value="naver.com">naver.com</option>
<option value="google.com">google.com</option>
<option value="hanmail.net">hanmail.net</option> -->
</select>
</div>
<div>
<h1>상품 수량</h1>
<select name="product-dropdown" id="product-dropdown">
<!-- <option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option> -->
</select>
</div>
<script src="./drop.js"></script>
</body>
</html>
interface DropDownItem<T> {
value: T;
selected: boolean;
}
const emails: DropDownItem<string>[] = [
{ value: "naver.com", selected: true },
{ value: "gmail.com", selected: false },
{ value: "hanmail.com", selected: false },
];
const numberOfProduct: DropDownItem<number>[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false },
];
// toString을 메서드를 가지고 있는 객체를 extends하여 에러를 해결
function createDropdownItem<T extends { toString: Function }>(
item: DropDownItem<T>
): HTMLOptionElement {
const option = document.createElement("option");
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// 배열에 foreach를 돌려가며 appendchild해 줌.
emails.forEach(function (email: DropDownItem<string>) {
const item = createDropdownItem<string>(email);
const selectTag = document.querySelector("#email-dropdown");
selectTag.appendChild(item);
});
numberOfProduct.forEach(function (productNumber: DropDownItem<number>) {
const item = createDropdownItem<number>(productNumber);
const selectTag = document.querySelector("#product-dropdown");
selectTag.appendChild(item);
});
Generic 타입 제한
앞서 아래와 같이 Generic에 extends {toString: Function}을 붙여서 특정 메서드를 가진 타입으로만 가능하도록 제한한 부분이 있습니다.
toString 메서드를 가진 타입만 사용하도록 만들 수 있습니다.
function createDropdownItem<T extends { toString: Function }> ... 중략
비슷하게, length 메서드가 있는 타입만 지정할 수 있도록 generic을 제한하는 방법은 다음과 같습니다.
아래 예시는 length 메서드가 있는 타입으로 제한하였습니다.
function logTextLenghth<T extends { length: number }>(text: T): number {
return text.length;
}
// 잘 안보이면 interface로 분리해보자
interface LengthType {
length: number;
}
function logTextLenghth<T extends LengthType>(text: T): number {
return text.length;
}
추가로, keyof를 사용하여 특정 객체의 키값만을 T로 넣을 수 있게할 수 있습니다.
아래 예시는 itemOption에는 name, price, stock만 올 수 있습니다. keyof에 대해 알고 계시면 이해할 수 있습니다.
interface ShoppingItem {
name: string;
price: number;
stock: number;
}
// ShoppingItem의 키 중에서 한가지가 Generic이 된다.
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
return itemOption;
}
console.log(getShoppingItemOption("name"));
console.log(getShoppingItemOption("price"));
console.log(getShoppingItemOption("stock"));
typescript에서 js querySelector를 정의한 부분을 사렾보신다면 keyof를 요긴하게 사용한 것을 확인하실 수 있습니다.
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
'Programming Language > 🟦 Typescript' 카테고리의 다른 글
ts 환경에서 외부 라이브러리 사용시 주의할 점 (+ typeRoots, esmoduleinterop) (0) | 2020.11.21 |
---|---|
tsconfig baseUrl 옵션에 의한 오류 바로잡기 (0) | 2020.11.03 |
type vs interface / union vs intersection (0) | 2020.10.31 |
Type assertions (타입 단언) (0) | 2020.10.21 |
배열과 튜플의 타이핑 및 간단한 tip(rest, generic ...) (0) | 2020.08.23 |