ts에서는 제네릭 클래스(Generic Class)를 통해 클래스 내에서도 제네릭 타입을 사용할 수 있게 지원한다. 제네릭 클래스는 여러 종류의 데이터 유형에 대해 재사용 가능한 클래스를 만드는 데 사용된다.
제네릭 클래스를 사용하면 클래스의 멤버(프로퍼티 및 메서드)에서도 일반적인 타입 대신 제네릭 타입을 사용할 수 있다.
class Box<T> {
private _value: T;
constructor(value: T) {
this._value = value;
}
get value(): T {
return this._value;
}
set value(value: T) {
this.value = value;
}
}
const numberBox = new Box<number>(15); // number 형태의 Box 생성
numberBox.value = 200; // setter 함수로 인해 가능
const stringBox = new Box<string>("Hello"); // string 형태의 Box 생성
stringBox.value = 12; // 이미 string으로 할당되었기 때문에 number 값을 넣으면 에러가 발생
또한 class에 generic을 여러개 넣는 것도 가능하다.
class Person<T, U> {
private _name: T;
private _age: U;
constructor(name: T, age: U) {
this._name = name;
this._age = age;
}
}
const p1: Person<string, number> = new Person<string, number>('go', 2134);
const p2: Person<string, number> = new Person('do', 'to'); // 'to' 에 에러 발생
generic class도 extends
를 사용할 수 있다. 하지만 일반적인 class의 상속과 거리가 있다.
다음 아래의 예시는 string
과 number
를 상속받는 뜻이 아닌, string
혹은 number
만이 T에 할당할 수 있다는 뜻에 가깝다.
ts개발자는 genereic class나 function을 만들 때, 이러한 extends 기능을 활용하여 generic <T>
에 어떤 type이 들어올 수 있는지 추측가능하게 만들어야 하는 것이 좋다.
class PersonExtends<T extends string | number> { // T에는 string or number만 할당 가능
constructor(public _name: T) { // 다음과 같이 초기화가 가능하다
}
public hello(){
console.log(`hi! ${this._name}!`);
}
}
const person1 = new PersonExtends<string>('Erick');
const person2 = new PersonExtends(61);
const person3 = new PersonExtends(true); // 에러
인터페이스나 타입의 속성 이름들을 추출하는 키워드이다. 이를 통해 객체의 속성 이름을 문자열 리터럴 타입으로 추출하거나, 타입 안에서 특정 속성의 타입을 참조할 수 있다.
keyof
를 사용하여 객체의 속성을 추출하면 문자열 리터럴 타입으로 해당 객체의 모든 속성 이름을 나타낸다. 이를 활용하여 유연한 타입 체크나 제네릭 타입을 만들 수 있다.
위와 같이 Person의 keyof
만들어진 PersonKey 타입은 값으로 'name'
또는 'string'
이 올 수 있다. 아래의 예제는 keyof
를 더 다양하게 사용한 예제이다.
interface Student {
name: string;
classNumber: number;
}
// U는 T의 prop 값이다.
function getValue<T, U extends keyof T>(obj: T, key: U): T[U] {
return obj[key];
}
function setValue<T, U extends keyof T>(obj: T, key: U, value: T[U]): void {
obj[key] = value;
}
// 각각의 get, set함수의 타입을 지정해보면 다음과 같다
// T[U] 는 T 객체의 속성에 들어있는 값이 온다. Gettter에 알맞는 형식이다.
type GetType = <T, U extends keyof T>(obj: T, key: U) => T[U];
// Setter는 값만 변경해줄 뿐 아무것도 리턴하지 않으므로 void를 준다.
type SetType = <T, U extends keyof T>(obj: T, key: U, value: T[U]) => void;
개발자는 특정 함수나 클래스를 제작할 때 이러한 Generic을 활용하여 타입을 지정해주는 것이 좋다. 후에 자신이나 다른 팀원이 사용할 때, 잘못된 사용으로 인한 타입 오류를 예방할 수 있고, 코드가 어떤 방향으로 흘러가는지 사전에 예측할 수 있기 때문이다. 따라서 Generic은 ts 코드의 품질을 높이고 유지보수성을 향상시키는 역할을 하는 중요한 기능이라 볼 수 있다.