급변하는 기술 시장에서 살아남기 위해 Typescript 공부를 시작하였습니다...
과연 저는 살아남을 수 있을까요?!
타입스크립트에는 제네릭 타입(generic type)이라는 것이 있습니다.
이번에는 이것이 무엇인지 알아보도록 하겠습니다.
이름에서 오는 낯선 향기...
쉽게 말해 제네릭은 타입에 변수를 제공하고,
배열 안의 요소까지 어떤 타입을 가졌는지 표기할 때 사용합니다.
다음과 같이 <>
를 이용해 표기합니다.
const numbersArr: Array<number>=[]; //number[] 와 같습니다.
배열의 경우
<>
안에는 배열 안에 들어갈 데이터들의 타입을 입력합니다.
만약 두가지 타입이라면 유니온 타입을,
여러 가지가 섞여있다면 any를 입력하면 됩니다.
배열 외에도 함수, 클래스 등에도 활용이 가능합니다.
제네릭 타입은 단순히 배열을 나타내는 것이 아니라
배열 안의 요소들에 대한 타입을 알려줍니다.
때문에 배열 안의 요소를 미리 파악하여 그에 대한 연산을 안전하게 할 수 있다는 장점이 있습니다.
즉 제네릭 타입을 사용하면 보다 나은 타입 안전성을 확보할 수 있습니다.
이번에는 제네릭을 활용하여 함수를 만들어 볼까요?
만약 일반 객체 2개를 매개변수로 받아 하나의 객체로 리턴하는 함수가 있다고 가정해 봅시다!
function mergeObj(obj1: object, obj2: object) {
return Object.assign(obj1, obj2);
}
const result1 = mergeObj({ id: 20230001, name: 'stella' }, { class: 'IT' });
여기서 만약 result1의 속성에 접근하려고 하면 에러가 납니다.
왜 일까요???
타입스크립트가 추론하기에 반환되는 object는 어떤 속성을 가지고 있고,
각 속성들의 타입이 어떤지 확실하지 않기 때문입니다.
이 때 제네릭을 사용하면 변수를 타입으로 전달하여
타입스크립트가 객체에 어떤 속성을 가지고 있는지 확실하게 알 수 있습니다.
마찬가지로 <>
를 사용해 위 예제를 아래와 같이 표시할 수 있습니다.
function mergeObj<T,U>(obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
const result1 = mergeObj({ id: 20230001, name: 'stella' }, { class: 'IT' });
정해진 것은 아니지만 통상적으로 대문자 T,U를 사용하며,
타입스크립트는 반환되는 타입을 T와 U의 인터섹션한 타입으로 받아들입니다.
이렇게 하면 타입스크립트가 함수에 의해 반환되는 객체의 속성을 명확하고 알 수 있고
에러가 나지 않습니다.
그런데 만약!
위 예제에서 전달인자로 숫자나 문자열 같은 원시타입을 전달하면 어떻게 될까요?
당연히 제대로 하나의 객체가 되지 않습니다.
받아야할 전달인자를 객체로 제약을 걸 순 없을까요?
바로 가능합니다!
function mergeObj<T extends object,U extends object>(obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
const result1 = mergeObj({ id: 20230001, name: 'stella' }, { class: 'IT' });
위 예제처럼 제네릭 타입에 extends
를 사용하면 전달인자의 타입에 제약 조건을 걸 수 있습니다.
extends
뒤에는 유니언 타입 등 어떤 타입이든 가능합니다!
제네릭 타입을 이용해 클래스를 만들 수 있습니다.
만약 특정한 값을 받아 이를 토대로 배열을 만드는 클래스가 아래 예제와 같이 있다고 가정해봅시다!
이 클래스는 배열의 요소들을 삽입하고, 삭제하고, 읽어들일 수도 있습니다.
class MakeArray{
private arr: any[] = [];
addItem(item) {
this.arr.push(item);
}
removeItem(item) {
if (this.arr.indexOf(item) === -1) {
return;
}
this.arr.splice(this.arr.indexOf(item), 1);
}
getItems() {
return [...this.arr];
}
}
이 예제를 그냥 쓰면 오류가 납니다.
그리고 타입스크립트 배열 안의 요소로 어떤 것이 들어올지 추론이 어렵고
배열 안의 요소로 객체가 들어오면 위 예제의 코드는 좀 더 손봐야해요.
이런 문제들은 제네릭 타입을 이용해서 해결할 수 있습니다!
class MakeArray<T extends string | number | boolean> {
private arr: T[] = [];
addItem(item: T) {
this.arr.push(item);
}
removeItem(item: T) {
if (this.arr.indexOf(item) === -1) {
return;
}
this.arr.splice(this.arr.indexOf(item), 1); // -1
}
getItems() {
return [...this.arr];
}
}
이렇게 제네릭 타입을 이용하면 안전하게 원시값만 받아들일 수 있고,
지금있는 코드를 조금만 수정하여 활용이 가능합니다.
이렇게 배열을 만드는 클래스를 잘 만들어 볼 수 있습니다!
이번에는 내용이 좀 길죠?
생소한 개념인 제네릭 타입에 대해 알아보았습니다.
좀 어렵더라도 예제를 살펴보면 이해하면 이해가 좀 쉽습니다...!
저는 다음 시간에도 살아남을 수 있을까요?!😂