타입을 마치 함수의 파라미터 개념인 것처럼 받게 되는 것이다.
C#, Java 등의 언어에서 재사용성이 높은 컴포넌트를 만들 때 자주 활용되는 특징이자 문법이다. 특히, 한가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는데 사용한다.
function logText<T>(text: T):T {
console.log(text);
return text;
}
logText('hi');
logText<string>('hi');
function logText(text) {
console.log(text);
return text;
}
logText('a');
logText(10);
logText(true);
logText()
함수에서 타입을 정의하지 않아 암묵적으로 타입이 any
라고 되어있기 때문에 어떤 타입도 받을 수 있는 것이다.function logText(text: string) {
console.log(text);
// text.split('').reverse().join(''); // 타입이 string일때 가능.
return text;
}
function logNumber(num: number) {
console.log(num);
return num;
}
logText('a');
logText(10);
logNumber(10);
logText(true);
string
타입과 number
타입 모두 가능하도록 해준 상태이다.function logText(text: string | number) {
console.log(text);
return text;
}
logText('a');
logText(10);
const a = logText('a');
a.split('') // split에 빨간 밑줄 에러가 발생한다.
string
타입과 number
타입 모두 적어줬지만, 문자열을 넣어줘도 string
에서 제공하는 split
이 에러가 난다.
타입이 string | number
에서는 string
을 제공하지 않기 때문이다. 즉, 정확한 타입을 넣어줘야만 사용가능하다는 문제점이 생기는 것이다.
인풋값은 해결되었지만, 반환값은 해결되지 않아서 문제가 발생한는 것이다.
제네릭은 타입 정의에 대한 이점을 가지고 있다. 함수를 정의할 때 타입을 비워놓고, 해당 함수를 호출한 시점에 타입을 정의하는 것이 제네릭이다.
제네릭을 사용하면 타입을 추론을 해서 최종 반환값까지 정의할 수 있다.
function logText<T>(text : T): T {
console.log(text);
return text;
}
// string이라는 타입을 받아서 쓰겠다고 호출할 때 정의하는 것이다.
// 파라미터(인자)와 반환값이 모두 string에 될 것이라고 타입스크립트 내부에서 제네릭을 이용해서 선언한 것이다.
const str = logText<string>('abc');
str.split(''); // string에 사용하는 split을 사용할 수 있다.
// 위의 함수의 같은 함수인 logText함수를 호출시 boolean을 타입으로 정의했다.
const login = logText<boolean>(true);
// 인터페이스에 제네릭을 선언하는 방법
interface Dropdown {
value: string;
selected: boolean;
}
const obj: Dropdown = { value: 'abc', selected: false };
const obj: Dropdown = { value: 10, selected: false }; // value값에는 string이라는 타입이 정의되어있기 때문에 에러가 발생한다.
Dropdown인터페이스
의 value
를 string
라고 정의했기 때문에 value에 숫자를 사용할 경우 에러가 발생하게 된다.이처럼 여러 타입을 사용하기 위해 인터페이스 선언이 늘어나게 되고 코드가 지저분해질 경우, 제네릭을 사용해 손쉽게 선언할 수 있다.
interface Dropdown<T> {
value: T;
selected: boolean;
}
const obj:Dropdown<string> = { value: 'abc', selected: false };
const obj:Dropdown<number> = { value: 10, selected: false };
Dropdown
이라는 인터페이스를으로 제네릭으로 정의하는데, 정의를 할 때 타입을 선언하는 시점에 타입을 추가적으로 넘겨서 Dropdown인터페이스
의 타입을 바꿔준다.프로젝트 실습 파일:
실습자료
-learn-typescript
-example
-dropdown-generic.html
,dropdown-generic.ts
const emails: Email[] = [
{ value: 'naver.com', selected: true },
{ value: 'gmail.com', selected: false },
{ value: 'hanmail.net', selected: false },
];
interface ProductNumber {
value: number;
selected: boolean;
}
const numberOfProducts: ProductNumber[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false },
];
function createDropdownItem(item: Email | ProductNumber) {
const option = document.createElement('option');
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (product) {
const item = createDropdownItem(product);
})
interface DropdownItem<T> {
value: T;
selected: boolean;
}
// 배열 안의 요소에 대한 타입을 정의해줘야 한다.
const emails: DropdownItem<string>[] = [
{ value: 'naver.com', selected: true },
{ value: 'gmail.com', selected: false },
{ value: 'hanmail.net', selected: false },
];
const numberOfProducts: DropdownItem<number>[] = [
{ value: 1, selected: true },
{ value: 2, selected: false },
{ value: 3, selected: false },
];
function createDropdownItem<T>(item: DropdownItem<T>) {
const option = document.createElement('option');
option.value = item.value.toString();
option.innerText = item.value.toString();
option.selected = item.selected;
return option;
}
// NOTE: 이메일 드롭 다운 아이템 추가
emails.forEach(function (email) {
const item = createDropdownItem<string>(email);
const selectTag = document.querySelector('#email-dropdown');
selectTag.appendChild(item);
});
numberOfProducts.forEach(function (product) {
const item = createDropdownItem<number>(product);
})
interface
에 제네릭을 이용해 여러 타입이 가능하도록 했다.createDropdownItem()
함수에도 제네릭을 이용함으로써 유니온 방식을 사용하지 않고 타입 정의가 가능해졌다.하나의 인터페이스로 제네릭을 이용해 여러가지 타입을 커버해 타입코드를 줄여나갈 수 있다는 것이 제네릭의 장점이다.
function logTextLength<T>(text: T): T {
console.log(text.length);
return text;
}
logTextLength('hi');
logTextLength('hi)
를 실행할 경우 console.log(text.length)
의 length에 빨간 밑줄이 생기게 된다. logTextLength()
에 어떤 타입이 들어올지 알 수 없기 때문에 length가 있다는 것은 개발자만 알고 있는 상태이다. (타입스크립트는 모름)length
를 사용하려면 <T>
라는 제네릭타입을 제한을 해야 한다. 힌트를 줘야 한다.function logTextLength<T>(text: T[]): T[] {
console.log(text.length);
text.forEach(function(text) {
console.log(text);
});
return text;
}
logTextLength<string>(['hi', 'abc']);
extends
클래스와 인터페이스를 다룰 때 상위에 이미 정의되어 있는 타입들을 확장할 경우 사용한다.
제네릭으로 정의된 타입을 특정 타입으로 제한하면 특정 타입의 하위 속성이 된다.
특정 타입에서 제공되는 속성을 기본적으로 가진 상태에서 추가로 속성을 더 정의할 수 있게 된다.
interface LengthType {
length: number;
}
function logTextLength<T extends LengthType>(text: T): T {
text.length;
return text;
}
logTextLength('a');
logTextLength({ length: 10 });
logTextLength(10); // number인 10은 사용 불가하다.
// 제네릭에 어느정도 타입 제한이 생기는 것을 볼 수 있다.
extends
를 이용해 <T>
의 타입을 LengthType
라는 타입으로 제한했다. LengthType
의 length
라는 속성을 이용할 수 있게 된다.선언된 인터페이스의 속성 중 하나만 받겠다고 제약할 수 있다.
interface ShoppingItem {
name: string;
price: number;
stock: number;
}
function getShoppingItemOption<T extends keyof ShoppingItem>(itemOption: T): T {
return itemOption;
}
getShoppingItemOption('name');
💡참고