본문 바로가기

복습/Typescript

[typescript] 타입스크립트 제네릭

typescript06

타입스크립트 제네릭

자바스크립트에는 없지만 타입스크립트에 있는 제네릭(Generics)을 알아보겠습니다. 클래스나 함수의 목적중 하나는 '재사용성'에 있습니다. 이 제네릭을 이용하게되면 재사용성이 한층 더 증가합니다. 일종의 '틀' 이라고 생각하시면 됩니다. 타입스크립트에서 제네릭을 어떻게 쓰는지에대해 알아보도록 하겠습니다.

📝 목차


기존의 문제점

예를들어 number, text, operator(연산자) 각 종류별로 버튼들이 존재할 때 이 버튼들을 담으려 할 때 담는 일종의 '그릇'을 각각 만들어줘야 할 것 입니다.

// TS
class NumberButton {}
class TextButton {}
class OprButton {}
class NumberButtonContainer {}
class TextButtonContainer {}
class OprButtonContainer {}
// ...

클래스 이름이 너무 길어졌습니다... 양해 부탁드립니다.

사실 위의 코드는 Container 라는것은 동일하고 그 내부 기능들도 동일하다고 가정을 한다면 제네릭을 이용하여 재사용성을 높이는 것이 더 좋을것 같습니다.

클래스에서의 제네릭

위의 코드에서 제네릭을 이용해 바꾸어보겠습니다.

// TS
class NumberButton {}
class TextButton {}
class OprButton {}
class ButtonContainer<T> {
private container: T[];
getButton(index: number) {
return this.container[index];
}
// ...
}
const numberContainer = new ButtonContainer<NumberButton>();
const textContainer = new ButtonContainer<TextButton>();
const oprContainer = new ButtonContainer<OprButton>();

주목해야할 점은 <T> 입니다. type 의 약자로 보통 T 를 쓰지만 변경할 수 있습니다. type 에서 따왔듯이 타입에따라 틀이 바뀐다는 점입니다.

생성할때도 마찬가지로 () 전에 <> 꺾쇠괄호 안에 타입을 주입합니다.

주의해야할 점

제네릭에서 클래스 타입의 static 멤버는 갖을 수 없습니다.

// TS
class ButtonConatiner<T> {
// public static foo: T; Error!
// TS2302: Static members cannot reference class type parameters.
// 생략
}

이유를 아직 못찾아서 찾게되면 추가하겠습니다.

함수에서의 제네릭

함수에서도 동일하게 제네릭을 사용할 수 있습니다.

// TS
function getFirst(array: any[]): any {
return array[0];
}

다음코드는 배열의 첫번째 요소를 반환하는 코드입니다. any를 사용한것을 볼 수 있는데 이것을 제네릭으로 바꿔보겠습니다.

// TS
function getFirst<T>(array: T[]): T {
return array[0];
}

함수의 이름 뒤에 <T> 가 들어간것을 확인할 수 있습니다. 그리고 호출할 때도 타입을 정해줍니다.

// TS
getFirst<number>([1, 2, 3]);
getFirst<string>(['h','e','e']);

제약조건

제네릭을 이용하였지만 해당 프로퍼티가 있는지 없는지에 대해 확신할 수 없습니다. 그래서 컴파일러는 다음 코드에 대해 에러를 뱉습니다.

// TS
function getHeight<T> (person: T) {
return person.height; // Error!
// TS2339: Property 'height' does not exist on type 'T'.
}

이 코드는 개발자 입장에서는 person 이 들어옴을 확신하고 작성했지만 컴파일러에서는 .height라는 프로퍼티가 있는지 없는지 알 수 없기 때문에 생긴 에러입니다. 때문에 컴파일러에게 확신을 주게끔 인터페이스를 생성해주고 extends 키워드로 제약조건을 만들어줍니다.

// TS
interface Heightwise {
height: number;
}
function getHeight<T extends Heightwise> (person: T) {
return person.height;
}
const myInfo = {
name: 'heecheolman',
height: 171,
};
const foo = {
name: 'bar',
};
console.log(getHeight(myInfo)); // 171
// console.log(getHeight(foo));
// .height 프로퍼티가 없습니다.

Heightwise 를 상속하여 해당 인터페이스를 구현한 '타입'임을 확신해주었습니다.

마무리

기존의 자바스크립트에서는 없었던 제네릭을 타입스크립트에서 사용할 수 있게되었습니다. 타입스크립트의 제네릭 또한 타입을 체크해주는 타입스크립트 문법일 뿐, 직접적인 자바스크립트 코드로 트랜스파일링 해주진 않습니다.

아직 배우는 입장이라 구체적으로 써보진 않았지만 Jbee 님의 마무리에 따르면 실제 개발보다는 라이브러리를 개발할 때 많이 사용한다고 하십니다.

참고문헌