[TypeScript] 타입스크립트 제네릭(Generic), Factory Pattern with Generics
글 작성자: 망고좋아
반응형
🎯 타입스크립트 제네릭(Generic)
- 제네릭이란 타입을 마치 함수의 파라미터처럼 사용하는 것을 의미한다.
- 정적 type 언어는 클래스나 함수를 정의할 때 type을 선언해야 한다.
- Generic은 코드를 작성할 때가 아니라 코드를 수행될 때(런타임) 타입을 명시한다.
- 코드를 작성할 때 식별자를 써서 아직 정해지지 않은 타입을 표시한다.
- 일반적으로 식별자는 T, U, V ...를 사용한다.
- 필드 이름의 첫 글자를 사용하기도 한다.
📝 generic 사용 이유
- 한 가지 타입보다 여러 가지 타입에서 동작하는 컴포넌트를 생성하는 데 사용된다.
- 재사용성이 높은 함수와 클래스를 생성할 수 있다.
- 여러 타입에서 동작이 가능하다. (한 번의 선언으로 다양한 타입에 재사용할 수 있다.)
- 코드의 가독성이 향상된다.
- 오류를 쉽게 포착할 수 있다.
- any타입을 사용하면 컴파일 시 타입을 체크하지 않는다.
- 타입을 체크하지 않아 관련 메서드의 힌트를 사용할 수 없다.
- 컴파일 시에 컴파일러가 오류를 찾지 못한다.
- generic도 any처럼 타입을 지정하지 않지만, 타입을 체크해 컴파일러가 오류를 찾을 수 있다.
📝 generic을 함수에서 사용하기
function sort<T>(item: T[]): T[] {
return item.sort();
}
const nums: number[] = [1, 2, 3, 4];
const chars: string[] = ["a", "b", "c", "d", "e", "f", "g",];
sort<number>(nums);
sort<string>(chars);
function logText<T>(text: T): T {
return text;
}
// 1과 2는 같은 의미
// #1
let str: <T>(text: T) => T = logText;
// #2
let str: {<T>(text: T): T} = logText;
- 함수를 호출할 때 함수 안에서 사용할 타입을 넘겨줄 수 있다.
📕 제네릭 인터페이스
interface GenericLogTextFn {
<T>(text: T): T;
}
function logText<T>(text: T): T {
return text;
}
let myString: GenericLogTextFn = logText; // Okay
// 인터페이스에 인자 타입을 강조
interface GenericLogTextFn<T> {
(text: T): T;
}
function logText<T>(text: T): T {
return text;
}
let myString: GenericLogTextFn<string> = logText;
📝 generic을 class에서 사용하기
class Queue<T> {
protected data: Array<T> = [];
push(item: T) {
this.data.push(item);
}
pop(): T | undefined {
return this.data.shift();
}
}
const numberQueue = new Queue<number>();
numberQueue.push(0);
numberQueue.push("1"); // 의도하지 않은 실수를 사전 검출 가능
numberQueue.push(+"1"); // 실수를 사전 인지하고 수정할 수 있다.
📝 Union type
// any를 사용하는 경우
function getAge(age: any) {
age.toFixe(); // 에러 발생, age의 타입이 any로 추론되기 때문에 숫자 관련된 API를 작성할 때 코드가 자동 완성되지 않는다.
return age;
}
// 유니온 타입을 사용하는 경우
function getAge(age: number | string) {
if (typeof age === 'number') {
age.toFixed(); // 정상 동작, age의 타입이 `number`로 추론되기 때문에 숫자 관련된 API를 쉽게 자동완성 할 수 있다.
return age;
}
if (typeof age === 'string') {
return age;
}
return new TypeError('age must be number or string');
}
|
를 사용해 두 개 이상의 타입을 선언하는 방식- Union과 Generic 모두 여러 타입을 다룰 수 있다.
- Union은 선언한 공통된 메서드만 사용할 수 있다.
- 리턴 값이 하나의 타입이 아닌 선언된 Union 타입으로 지정된다.
📝 제약조건(Constraints / keyof)
- 원하지 않는 속성에 접근하는 것을 막기 위해 Generic에 제약조건을 사용한다.
📕 Constraints
const printMessage = <T extends string | number>(message: T):T => {
return message;
}
printMessage<string>("1");
printMessage<number>(1);
printMessage<boolean>(false); // error : Type 'boolean' does not satisfy the constraint 'string | number'.
extends
키워드로 제약조건을 걸어준다.- 특정 타입들로만 동작하는 generic 함수를 만들 때 사용한다.
- Generic T에 제약 조건을 설정한다.(문자열 or 숫자)
- 제약 조건을 벗어나는 타입을 선언하면 에러가 발생한다.
📕 keyof
const getProperty = <T extends object, U extends keyof T>(obj: T, key: U) => {
return obj[key];
}
getProperty( { a: 1, b: 2, c: 3 }, "a");
getProperty( { a: 1, b: 2, c: 3 }, "z"); // error : Argument of type '"z"' is not assignable to parameter of type '"a" | "b" | "c"'.
- 두 객체를 비교할 때 사용한다.
- 위 예제에서는 두 번째 함수에서 오류가 발생한다.
- Generic T는 키 값이 a, b, c만 존재하는 object이다.
- U의 값인 'z'가 Generic T의 키 값 중 존재하지 않기 때문에 오류가 발생한다.
📝 Factory Pattern with Generics
interface Car {
drive(): void;
park(): void;
}
class Bus implements Car {
drive(): void {}
park(): void {}
}
class Taxi implements Car {
drive(): void {}
park(): void {}
}
class CarFactory {
static getInstance(type: String): Car {
// car의 type이 추가될 때마다, case 문을 추가해야 하는 단점
switch (type) {
case "bus":
return new Bus();
default:
return new Taxi();
}
}
}
const bus = CarFactory.getInstance("bus");
const taxi = CarFactory.getInstance("taxi");
🛠 디자인 패턴 적용
interface Car {
drive(): void;
park(): void;
}
class Bus implements Car {
drive(): void {}
park(): void {}
}
class Taxi implements Car {
drive(): void {}
park(): void {}
}
class Suv implements Car {
drive(): void {}
park(): void {}
}
class CarFactory {
static getInstance<T extends Car>(type: { new (): T}): T {
return new type();
}
}
const bus = CarFactory.getInstance(bus);
const taxi = CarFactory.getInstance(taxi);
- 객체를 생성하는 인터페이스만 미리 정의하고, 인스턴스를 만들 클래스의 결정은 서브 클래스가 내리는 패턴
- 여러 개의 서브 클래스를 가진 슈퍼 클래스가 있을 때, 입력에 따라 하나의 서브 클래스의 인스턴스를 반환한다.
📌 참고
반응형
'프로그래밍 > TypeScript' 카테고리의 다른 글
[TypeScript] 타입스크립트 타입 가드 (Type Guard) (0) | 2021.11.29 |
---|---|
[TypeScript] 타입스크립트 유니온 타입 & 인터섹션 타입 (Union Type & Intersection Type) (0) | 2021.11.29 |
[TypeScript] 타입스크립트 인터페이스(Interface), Strategy pattern (0) | 2021.11.27 |
[TypeScript] 타입스크립트 Getter & Setter / readonly / static (0) | 2021.11.24 |
[TypeScript] 타입스크립트 Class 접근 제어자 (0) | 2021.11.24 |
댓글
이 글 공유하기
다른 글
-
[TypeScript] 타입스크립트 타입 가드 (Type Guard)
[TypeScript] 타입스크립트 타입 가드 (Type Guard)
2021.11.29 -
[TypeScript] 타입스크립트 유니온 타입 & 인터섹션 타입 (Union Type & Intersection Type)
[TypeScript] 타입스크립트 유니온 타입 & 인터섹션 타입 (Union Type & Intersection Type)
2021.11.29 -
[TypeScript] 타입스크립트 인터페이스(Interface), Strategy pattern
[TypeScript] 타입스크립트 인터페이스(Interface), Strategy pattern
2021.11.27 -
[TypeScript] 타입스크립트 Getter & Setter / readonly / static
[TypeScript] 타입스크립트 Getter & Setter / readonly / static
2021.11.24