header wave

Post

TS) Enum과 Generic

2022-09-28 AM 05/32
#typescript
#study

1. 열거 타입(Enum)

일정 수의 숫자나 문자열로 된 집합

  • 숫자형의 열거

// enum 타입으로 요일 정의

enum Weekdays = {
	월요일 = 1,
	화요일 = 2,
	수요일 = 3,
	목요일 = 4,
	금요일 = 5,
	토요일 = 6,
	일요일 = 7,
}

let dayOff = Weekday.월요일;

enum은 자동증가 기능이 있다.

enum Weekdays = {
	월요일 = 1,
	화요일,
	수요일,
	목요일,
	금요일,
	토요일,
	일요일,
}

console.log(Weekdays[3)= 수요일

enum을 사용하는 이유

function convertTemperature(temp: number, fromTo: string): number { 

  return ('FtoC' === fromTo) ?
            (temp - 32) * 5.0/9.0:  
            temp * 9.0 / 5.0 + 32;  
}

console.log(`70F is ${convertTemperature(70, 'FtoC')}C`); 
console.log(`21C is ${convertTemperature(21, 'CtoF')}F`); 
console.log(`35C is ${convertTemperature(35, 'ABCD')}F`);

console.log(`35C is ${convertTemperature(35, 'ABCD')}F`); 

문자열이지만 잘못된 값인 ‘ABCD’가 매개변수로 들어왔다. 이럴 때 오류를 감지하고 싶다면? enum을 쓰면 된다.

enum Direction {
	FtoC,
	CtoF,
}

function convertTemperature(temp: number, fromTo:  Direction): number { 

  return Direction.FtoC === fromTo ? (temp - 32) * 5.0/9.0 :  temp * 9.0 / 5.0 + 32;  
          
}

console.log(`70F is ${convertTemperature(70, Direction.FtoC)}C`); 
console.log(`21C is ${convertTemperature(21, Direction.CtoF)}F`);

  • 문자열 열거

enum Direction {
	Up = 'UP',
	Down = 'DOWN',
	Left = 'LEFT',
	Right = 'RIGHT'
}

function move(where: Direction){
	if(where === Direction.Up {
		// move
	}
}

move("남쪽"); // Error

enum with redux

enum ProductsActionTypes {
	Search = 'Products Search',
	Load = 'Porducts Load All',
}

console.log(ProductsActionTypes.Search);

const enum

const enum을 사용하면 자바스크립트가 생성되지 않는다.

제네릭(Generic)

제네릭은 왜 쓰는 것인가?

숫자나 문자열 같은 특정한 타입을 파라미터로 받는 함수를 선언하기는 쉽다.

function calcTax(income: number, state: string){}

타입스크립트를 제네릭을 사용하면 다양한 타입을 지원하는 함수를 작성할 수 있다.

즉, 제네릭을 사용해 함수를 선언하면, 함수의 호출자가 나중에 구체적인 타입을 지정할 수 있다.

타입스크립트는 제네릭 함수, 클래스 또는 인터페이스를 작성할 수 있다.

제네릭 타입은 T in Array와 같은 임의의 문자로 표시한다.

배열 내, 요소 타입을 선언할 때 <> 기호 안에 해당 타입을 작성한다.

class Car{};

const cars = new Array<Car>(10);

// Car 타입인 객체가 10개인 Array 배열을 생성하며, Car[] 타입을 유추할 수 있다.

class Animal {
	eatable: boolean;
}

class Cow extends Animal {
	weight: number;
}

class Fly {
	color: string;
}

const animals: Array<Person> = []; // 제네릭 타입을 선언

animals[0] = new Animal();
animals[1] = new Cow();
animals[2] = new Fly(); // complie error

제네릭의 구조적 타입 시스템

class Animal {
	eatable: boolean;

	
}

const test1 = {eatable: false};

const test2 = new Animal()

console.log(test2 instanceof Animal) // true
console.log(test1 instanceof Animal) // false

const arr:Array<Animal> = [];

arr[0]= test1; // ok
arr[1] = {gogo:true} // compile error

제네릭 타입은 많은 경우에 사용될 수 있다.

다양한 타입의 값을 취하는 함수를 만들 수도 있지만, 호출 중에 구체적인 타입을 명시적으로 작성해야 한다.

클래스, 인터페이스, 함수와 함께 제네릭 타입을 사용하기 위한 작성법이 있다.

// 제네릭 배열

// 배열의 모든 요소가 같다면 value1이 읽고 쓰기 간단

const value1: string[] = ['kim', 'lee'];
const value2: Array<string> = ['kim', 'lee'];

// 배열의 모든 요소가 다르다면 제네릭
const value3: Array<string | number> = ['kim', 'lee', 123];

제네릭 타입 생성

interface Comparator {
	compareTo(value: any): number;
}

interface Comparator {
	compareTo(value: any): number;
}

class Rectangle implements Comparator {
	compareTo(value: any): number{
		// logic
	}
}

class Triangle implements Comparator {
	compareTo(value: any): number {
		// logic
	}
}


retangle1.compareTo(triangle1); // value 값이 any 이므로 이런 연산이 가능하다.

interface Comparator<T> {
    compareTo(value: T): number;
}

class Rectangle implements Comparator<Rectangle> {

    constructor(private width: number, private height: number){};

    compareTo(value: Rectangle): number{
        return this.width * this.height - value.width * value.height;
    }
}

const rect1:Rectangle = new Rectangle(2,5);
const rect2: Rectangle = new Rectangle(2,3);

rect1.compareTo(rect2) > 0 ? console.log("rect1 is bigger"): 
   rect1.compareTo(rect2) == 0 ? console.log("rectangles are equal") :
                            console.log("rect1 is smaller") ;


class Programmer implements Comparator<Programmer> {

    constructor(public name: string, private salary: number){};

    compareTo(value: Programmer): number{
        return this.salary - value.salary;
    }  
}

const prog1:Programmer = new Programmer("John",20000);
const prog2: Programmer = new Programmer("Alex",30000);

prog1.compareTo(prog2) > 0 ? console.log(`${prog1.name} is richer`):
      prog1.compareTo(prog2) ==  0? 
           console.log(`${prog1.name} and ${prog1.name} earn the same amounts` ) :
           console.log(`${prog1.name} is poorer`) ;

제네릭 타입의 기본값

제네릭 타입을 사용하기 위해, 상세 타입을 제공해야 한다.

class A <T> {
	value: T;
}

class B extends A { // compile error
}

class B extends A<any>{ // ok
}

다른 방법으로, 제네릭 타입 선언 시 기본 파라미터 타입을 추가하는 방법이 있다.

class A<T = any> {
	value T;
}

class B extends A { // ok

}

// 임시 타입도 가능
class A <T = {}> {
	value T;
}

제네릭 함수 생성

any 타입을 사용한 함수

function printMe(content: any): any{
	console.log(content);
	return content;
}

const a = printMe('Hello'); 

class Person {
	constructor(public name:string){}
}

const b = printMe(new Person('Kim'));

제네릭 함수

function printMe <T>(content: T):T {
	console.log(content);
	return content;
}

const a = printMe('Hello');

class Person {
	constructor(public name: string){}
}

const b = printMe(new Person('Kim'));

화살표 함수 내 제네릭 타입 사용

const printMe = <T>(content: T):T => {
	console.log(content);
	return content;
}

const a = printMe('Hello');

class Person {
	constructor(public name: string){}
}

const b = printMe(new Person('Kim'));


const a = printMe<string>('Hello');

함수에 명시적으로 과 같이 타입을 적어 표현할 수 있다.

하지만, 타입스크립트 컴파일러는 a의 타입을 문자열로 유추하므로 명시적으로 타입을 사용할 필요는 없다.

(상황마다 케바케일 수 있음)

class Pair <K, V> { // 두 개의 타입 파라미터를 가진 클래스 선언
	key: K; // 제네릭 타입 K의 프로퍼티를 선언
	value: V; // 제네릭 타입 V의 프로퍼티를 선언
}

class Pair<K, V> {
  constructor(public key: K, public value: V) {}
}

function compare <K,V> (pair1: Pair<K,V>, pair2: Pair<K,V>): boolean {
    return pair1.key === pair2.key &&
           pair1.value === pair2.value;
}

let p1: Pair<number, string> = new Pair(1, "Apple");

let p2 = new Pair(1, "Orange");

// Comparing apples to oranges
console.log(compare<number, string>(p1, p2));  // prints false

let p3 = new Pair("first", "Apple");

let p4 = new Pair("first", "Apple");

// Comparing apples to apples
console.log(compare(p3, p4));  // prints true

console.log(compare(p3, p1));  // compile error 왜 에러가 날까?

interface User { 
    name: string;
    role: UserRole;
}

enum UserRole {  
    Administrator = 'admin',
    Manager = 'manager'
}

function loadUser<T>(): T {  
    return JSON.parse('{ "name": "john", "role": "admin" }');
}

const user = loadUser<User>(); 

switch (user.role) {  
    case UserRole.Administrator: console.log('Show control panel'); break;
    case UserRole.Manager: console.log('Hide control panel'); break;
}

고차함수 내 반환 타입 강제

함수를 파라미터로 받거나, 다른 함수를 반환하는 함수를 고차함수라고 한다.

(c: number) => number

(someValue: number) => (multiplier: number) => someValue * multiplier;

고차함수의 사용

const outerFunc = (someValue: number) => 
	(multiplier: number) => someValue * multiplier;

const innerFunc = outerFunc(10);

let result = innerFunc(5);

console.log(result);

제네릭을 사용해서 파라미터 타입이 다른 고차함수가 호출되더라도, 동일한 함수 시그니처를 만들어 보자.

type numFunc<T> = (arg: T) => (x: number) => number; 

const noArgFunc: numFunc<void> = () => (c: number) => c + 5;
const numArgFunc: numFunc<number> = (someValue: number) =>
                         (multiplier: number) => someValue * multiplier;
const stringArgFunc: numFunc<string> = (someText: string) =>
                        (padding: number) => someText.length + padding;

const createSumString: numFunc<number> = () => (x: number) => 'Hello';  //error

요약

  • enum 키워드를 통해 제한된 수의 상수들로 이루어진 집합을 선언할 수 있다.
  • enum은 숫자 또는 문자열에 이름을 지정할 수 있다.
  • const enum은 그 값이 인라인 되면서 자바스크립드가 생성되지 않는다.
  • 제네릭은 코드가 실행될 때 적혀있는 다양한 타입의 값을 이용하게 해준다.
  • 타입 파라미터를 가진 클래스, 인터페이스, 함수를 작성할 수 있다.