Typescript : 기초부터 실전형 프로젝트까지 with React + NodeJS / 5. 고급타입, 6. 제네릭, 7. 데코레이터

hatban·2023년 3월 22일
0
post-thumbnail

고급타입

1. 인터섹션타입

기존 타입을 대체하지 않으면서 기존 타입에 새로운 필드를 추가

  • & 사용
  • Type A, Type B 를 인터섹션하면 A이면서 B라는 의미

유니온타입을 인터섹션하면?

type Combinable = string | number;
type Numeric = number | boolean;
type Universal = Combinable &  Numeric;
  • Universal은 number타입이다 => 공통 부분만 해당됨

2. 타입가드

  • 특정 속성이나 메소드를 사용하기 전에 존재 여부를 판단하는 것
type Combinable = string | number;
type Numeric = number | boolean;

function add(a:Combinable, b: Combinable) {
    if(typeof a === 'string' || typeof b ==='string'){ //둘중에 하나는 무조건 string => 타입가드
        return a.toString() + b.toString();
    }
    return a+b; //둘다 string이 아니란것을 만족함
}

1. 사용자 정의 타입을 타입가드하기

type UnknownEmployee = Employee | Admin;
function printEmloyeeInfo(emp: UnknownEmployee) {
  console.log('Name ' + emp.name);
  if ('privileges' in emp) {
    console.log('Privileges : ' + emp.privileges);
  }
  if ('startDate' in emp) {
    console.log('StartDate : ' + emp.startDate);
  }
}
  • typeof emp === 'Employee'와 같은것은 불가능하기 때문에 in 을 사용한다

2. 클래스를 타입가드하기

class Car {
  drive() {
    console.log('Driving...');
  }
}
class Truck {
  drive() {
    console.log('Driving a truck...');
  }
  loadCargo(amount: number) {
    console.log('Loading cargo ... ' + amount);
  }
}
type Vehicle = Car | Truck;
const v1 = new Car();
const v2 = new Truck();
function useVehicle(vehicle: Vehicle) {
  vehicle.drive();
/*   if ('loadCargo' in vehicle) {
    vehicle.loadCargo(5);
  } */
  if(vehicle instanceof Truck){
    vehicle.loadCargo(5);'
  }
}

3. 인터페이스를 타입가드하기

  • 인터페이스는 instanceof를 사용할 수 없다
  • 구별된 유니온을 만들자
interface Bird {
  type: 'bird'; //type이라고 지정할 필요x 그냥 구분을 위한 이름이다
  flyingSpeed: number;
}
interface Horse {
  type: 'horse';
  runningSpeed: number;
}
type Animal = Bird | Horse;
function moveAnimal(animal: Animal) {
  /*     if('flyingSpeed' in animal){
        console.log('Moving with Speed: ' + animal.flyingSpeed);
    } */
  switch (animal.type) {
    case 'bird':
      console.log('Moving with Speed: ' + animal.flyingSpeed);
      return;
    case 'horse':
      console.log('Moving with Speed: ' + animal.runningSpeed);
      return;
    default:
      return;
  }
}

3. 함수 오버로딩

동일한 이름에 매개변수만 다른 여러번전의 함수를 만드는 것 (화살표 함수 x)

type Combinable = string | number;
type Numeric = number | boolean;

type Universal = Combinable & Numeric;

function add(a: number, b: number): number;
function add(a: number, b: string): string;
function add(a: string, b: number): string;
function add(a: string, b: string): string;
function add(a: Combinable, b: Combinable) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}

const result1 = add('Max', 'Schwarz');
result1.split(' ');
const result2 = add(2, 1);
// result2.split(' ') => number라 split불가

4. 옵셔널 체이닝

접근하는 객체의 프로퍼티가 null 또는 undefined일 수 있는 optional property인 경우 if 문을 사용하지 않고 넘어가게 하는 체이닝 방법이다.

const fetchUserData = {
  id: 'u1',
  name: 'max',
  // job: { title: 'CEO', description: 'My Own Company' },
};

//만약 데이터를 가져올때 이 데이터가 값이 없을수도 있고 못가져올 수도 있다
console.log(fetchUserData?.job?.title);

5. null 병합

null, undefined를 제외한 falsy 값을 그대로 리턴 하고 싶은 경우 사용한다.

  • ?? 사용
const userInput = '';

/* const storedData = userInput || 'DEFAULT';
console.log(storedData); //DEFAULT 
*/
const storedData = userInput ?? 'DEFAULT';
console.log(storedData); //빈문자열 


제네릭

타입을 마치 함수의 파라미터처럼 사용하는 것

사용이유

  • 한 가지 타입보다 여러가지 타입에서 동작하는 컴포넌트를 생성하는데 사용
  • 재사용성이 높은 함수와 클래스를 생성

1. 함수에서 제네릭 사용

function merge<T extends object, U extends object>(objA: T, objB: U) {
  return Object.assign(objA, objB);
}

console.log(merge({ name: 'Max' }, { age: 30 }));

const mergedObj = merge<{ name: string; hobbies: string[] }, { age: number }>(
  { name: 'Max', hobbies: ['Sports'] },
  { age: 30 }
);

특정 메소드나 속성이 있음을 명확히 해주기위해 인터페이스를 사용

interface Lengthy {
  length: number;
}
function countAndPrint<T extends Lengthy>(element: T): [T, string] {
  let decription = 'Got no vlaue.';
  if (element.length === 1) {
    decription = 'Got 1 element.';
  } else if (element.length > 1) {
    decription = 'Got ' + element.length + ' elements.';
  }
  return [element, decription];
}
console.log(countAndPrint('Hi there'));
console.log(countAndPrint(['Sports', 'Cooking']));
//console.log(countAndPrint(1));

keysof, extends 활용(제약조건)

function extractAndConvert<T extends object, U extends keyof T>(
  obj: T,
  key: U
) {
  return 'Value: ' + obj[key];
}
console.log(extractAndConvert({ name: 'MAX' }, 'name'));
//console.log(extractAndConvert({ name: 'MAX' }, 'age'));

2. 클래스에서 제네릭 사용

class DataStorage<T extends string | number | boolean> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    if (this.data.indexOf(item) === -1) {
      return;
    }
    //원시값이 아닌 객체나 배열이 전달되는 경우 실행 불가능, 항상 -1 출력
    this.data.splice(this.data.indexOf(item), 1);
  }

  getItems() {
    return [...this.data];
  }
}

const textStorage = new DataStorage<string>();
const numberStorage = new DataStorage<number>();


데코레이터

  • 메타프로그래밍에 유용

메타프로그래밍이란?

  • 개발자가 사용하기 쉬운 도구를 제공하는데 적합

특징

  • 데코레이터는 클래스 선언, 메서드(method), 접근자(accessor), 프로퍼티(property) 또는 매개변수(parameter)에 첨부할 수 있는 특수한 종류의 선언이다
  • 데코레이터 함수에는 target(현재타겟), key(속성이름), descriptor(설명)가 전달된다 (단, 어떤 멤버를 장식했느냐에 따라 인수가 달라짐)
  • 메소드나 클래스 인스턴스가 만들어지는 런타임에 실행. 즉, 매번 실행되지 않는다.
  • 데코레이터는 클래스 또는 클래스 내부의 생성자, 프로퍼티 , 접근자, 메서드, 그리고 매개변수에만 장식될 수 있다

tsconfig 파일 수정

    "experimentalDecorators": true 

데코레이터 팩토리

데코레이터 함수를 감싸는 래퍼함수

  • 보통 데코레이터가 선언에 적용되는 방식을 원하는데로 바꾸고 싶을 때 사용
  • 사용자로부터 인자를 전달 받도록 설정 가능

멀티 데코레이터

데코레이터는 하나의 멤버에 동시에 여러개의 데코레이터를 장식할 수 있다

  • 각 데코레이터의 표현식은 위에서 아래로 평가
  • 실행 결과는 아래에서 위로 함수 호출
// Size 데코레이터 팩토리
function Size() {
   console.log('Size(): 평가됨');
   // Size 데코레이터
   return function (target: any, prop: string, desc: PropertyDescriptor) {
      console.log('Size(): 실행됨');
   };
}
// Color 데코레이터 팩토리
function Color() {
   console.log('Color(): 평가됨');
   // Color 데코레이터
   return function (target: any, prop: string, desc: PropertyDescriptor) {
      console.log('Color(): 실행됨');
   };
}
// Button 클래스 정의
class Button {
   // 메서드에 멀티 데코레이터 적용
   @Size()
   @Color()
   isPressed() {}
}

/*
Size(): 평가됨
Color(): 평가됨
Color(): 실행됨
Size(): 실행됨
*/

0개의 댓글