타입스크립트

Typescript 공부하기 5일차 (2022.09.28)

개발자A입니다 2022. 9. 28. 22:59

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");