
타입을 마치 함수의 파라미터 개념인 것처럼 받게 되는 것이다.
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');
💡참고