Typescript 공부하기 5일차 (2022.09.28)
1. Generic
1) Generic란?
- 데이터 타입을 일반화 한다는 것을 의미
- 정적 type 언어는 클래스나 함수를 정의할 때 type을 선언해야 한다.
ㄴ ex) C언어는 int type 변수를 선언하면 정수형 값만 할당할 수 있다.
- Generic은 코드를 작성할 때가 아니라 코드가 수핸될 때 타입을 명시한다.
- 코드를 작성할 때 식별자를 써서 아직 정해지지 않은 타입을 표시한다.
2) Generic을 사용하는 이류
- 재사용성이 높은 함수와 클래스 생성 가능
ㄴ 여러 타입에서 동작 가능
ㄴ 코드의 가독성 향상
- 오류를 쉽게 포착 가능
ㄴ any 타입을 사용하면 컴파일 시 타입을 체크하지 않는다.
ㄴ 타입을 체크하지 않아 관련 메소드의 힌트를 사용할 수 없다.
ㄴ 컴파일 시에 컴파일러가 오류를 찾지 못한다.
- Generic도 any처럼 미리 타입을 지정하지는 않지만 타읍을 체크하여 컴파일러가 오류를 찾을 수 있다.
// any타입 사용은 지양, 다양한 타입을 사용해야할 때에는 Generic 혹은 Union 사용을 권장
2. Generic을 사용해 function과 class만들기
1) Function
function sort<T>(items: T[]): T[] {
return items.sort();
}
const nums: number[] = [1,2,3,4,5];
const chars: string[] = ['a'. 'b'. 'c'. 'd'. 'e'];
sort<number>(nums);
sort<string>(chars);
sort<number>(chars); // 타입이 일치하지 않아 오류 발생
sort<string>(nums); // 타입이 일치하지 않아 오류 발생
2) 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'); // string으로 입력되어 오류
numberQueue.push(+'1'); // number가 아님으로 오류
3. Union type
1) Union type이란?
- "|"을 사용하여 두 개 이상의 타입을 선언하는 방식
- Union과 Generic 모두 여러 타입을 다룰 수 있다.
ㄴ Union은 선언한 공통된 메소드만 사용할 수 있다.
ㄴ 리턴 값이 하나의 타입이 아닌 선언된 Union 타입으로 지정된다.
2) 예제 코드
// 1. Union Type
const printMessage = (message: string | number) => {
return messsage;
}
const message1 = printMessage(12345);
const message2 = printMessage("Hello World!");
message.length; // length는 string에서는 존재하지 않아서 오류 발생
// 2. Generic
cosnt printMessage2 = <T>(message:T) => {
return message;
}
const message1 = printMessage2<String>("Hello World!");
message.length;
4. 제약조건 (Constraints / keyof)
1) 제약조건의 용도
- 원하지 않는 속성에 접근하는 것을 막기위해 Generic에 제약조건을 사용
ㄴ Constrains: 특정 타입들로만 종작하는 Generic 함수를 만들 때 사용
ㄴ keyof: 두 객체를 비교할 때 사용
2) Constraints
- Generic T에 제약 조건을 설정한다. (문자열 or 숫자)
- 제약 조건을 벗어나는 타입을 선언하면 에러가 발생한다.
3) keyof
const getProperty = <T extends object, U extends keyof T>(obj: Tm key: U) => {
return obj[key]
}
getProperty({a:1, b:2, c:3}, "a");
getProperty({a:1, b:2, c:3}, "z"); // 오류, U의 값인 'z'가 Generic T의 키 값 중 존재하지 않기 때문
- 위의 예시에서 Generic T는 키 값이 a, b, c만 존재하는 object이다.
5. Design pattern (Factory Pattern with Generics)
1) Factory Pattern with Generics란?
- 객체를 생성하는 인터페이스만 미리 정의 하고, 인스턴스를 만들 클래스의 결정은 서브 클래스가 내리는 패턴
- 여러 개의 클래스를 가진 슈퍼 클래스가 있을 때, 입력에 따라 하나의 서브 클래스의 인스턴스를 반환
2) Factory Pattern
- 일반적으로 사용하는 것은 아래와 같은 형태이다.
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");
3) Factory Pattern with Generics
- 위의 코드에서는 car의 type 추가 시 case문의 계속 만들어야하는 단점이 있으며, 아래 예시에서는 suv가 추가되었다.
- 위의 코드 단점을 보완하여 아래와 같은 코드를 사용할 수 있다.
// 이전과 동일
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 {}
}
// 이전과 차이점
export CarFactory {
static getInstance<T extends Car>(type: { new(): T}): T{
return new type();
}
}
// 이전과 동일
const bus = CarFactory.getInstance("bus");
const taxi = CarFactory.getInstance("taxi");