Typescript 심화 문법

Seung Hyeon ·2023년 10월 21일
0

스터디

목록 보기
2/2
post-thumbnail

Type Guard

  • 데이터 타입을 알 수 없거나, 될 수 있는 타입이 여러개라 가정할 때 조건문을 통해 type을 추론할 수 있다.
  • 데이터의 타입에 따라 대응하여 에러를 최소회할 수 있음

타입 가드 예시

type SuccessResponse = {
  status: true;
  data: any;
};

type FailureResponse = {
  status: false;
  error: Error;
};

type CustomResponse = SuccessResponse | FailureResponse;

declare function getData(): CustomResponse;

const response: CustomResponse = getData();

// status 프로퍼티로 추론하자
if (response.status) {
  // status가 true인 경우: SuccessResponse 타입
  console.log(response.data);
} else if (response.status === false) {
  // status가 false인 경우: FailureResponse 타입
  console.log(response.error);
}

좀 더 심화 ↓↓

type SuccessResponse = {
  status: true
  data: any;
};

type FailureResponse = {
  status: false
  error: Error;
};

type CustomResponse = SuccessResponse | FailureResponse;

function typeGuard() {
  const response = getData();
  console.log(response.status);  // 💙
  if (response.status === false) {
  console.log(response.error.message);  // 💛
} else if (response.status === true) {
  console.log(response.data);    // 💛
}
}

function getData(): CustomResponse {
  if (Math.random() < 0.5) {
    return { status: false, error: new Error('error!') };
  }
  return {
    status: true,
    data: 'success!',
  };
}

typeGuard(); 
// Math.random값이 0.5미만이면 
// false  💙
// error! 💛

// Math.random값이 0.5이상이면 
// true   💙
// success!  💛

1. instance of

  • 클래스도 타입이다! 객체가 어떤 클래스의 객체인지 구별할 때 사용
  • 인스턴스 instanceof 클래스 형식
class Developer {
  develop() {
    console.log(`I'm working`);
  }
}

class Designer {
  design() {
    console.log(`I'm working`);
  }
}

const work = (worker: Developer | Designer) => {
  // worker인스턴스가 Desinger의 객체일 때
  if (worker instanceof Designer) {
    worker.design();
  } else if (worker instanceof Developer) {
    worker.develop();
  }
};

2. typeof

  • 데이터의 타입을 반환하는 연산자
  • type of 데이터 === ‘number’ 형식
const add = (arg?: number) => {
  if (typeof arg === "undefined") {
    return 0;
  }
  return arg + arg;
};

3. in

  • 문자열 A in Object 형식
  • Object의 key 중에 문자열 A가 존재하는가
type Human = {
  think: () => void;
};
type Dog = {
  tail: string;
  bark: () => void;
};

declare function getEliceType(): Human | Dog;

const elice = getEliceType();
// elice 오브젝트의 key에 tail이 있는 경우
if ("tail" in elice) {
  elice.bark();
} else {
  elice.think();
}

4. literal type guard

  • 리터럴 타입: 특정 타입의 하위 타입(구체적인 타입)
  • 리터럴 타입은 === 혹은 switch로 가능

※ 속도 측면: switch >> if . 따라서 switch 사용 권장

type Action = "click" | "hover" | "scroll";

const doPhotoAction = (action: Action) => {
  // 1번쨰 방법: switch 이용
  switch (action) {
    case "click":
      showPhoto();
      break;
    case "hover":
      zoomInPhoto();
      break;
    case "scroll":
      loadPhotoMore();
      break;
  }
};

const doPhotoAction2 = (action: Action) => {
  // 2번째 방법: === 연산자(if문) 이용
  if (action === "click") {
    showPhoto();
  } else if (action === "hover") {
    zoomInPhoto();
  } else if (action === "scroll") {
    loadPhotoMore();
  }
};

5. 사용자 정의 함수

  • 오픈소스 sindresorhus/is 이용
import is from "@sindresorhus/is";

const getString = (arg?: string) => {
  if (is.nullOrUndefined(arg)) {
    return "";
  }

  if (is.emptyStringOrWhitespace(arg)) {
    return "";
  }
  return arg;
};

Optional chaining

  • 객체가 null 또는 undefined이면 undefined를 리턴하고 그렇지 않은 경우 데이터 값을 리턴
  • ?. 형식
    • &&?.의 차이
      && : falsy(false, null, undefined, ‘’, 0, -0, NaN) 값 체크
      ?. : null과 undefined만 체크
  • 활용법
    • 객체 접근: obj?.name obj가 null, undefined가 아니면 name을 리턴
    • 배열 접근: arr?.[0] arr이 null, undefined가 아니면 첫번째 요소를 리턴
    • 함수 호출: func?.() 함수 func이 null, undefined가 아니면 func 함수 호출

예시

type Dog = {
  tail?: {
    softly: () => string;
  }
}

function tailSoftly(dog: Dog): string {
  return dog.tail?.softly()
}   

이때, dog.tail이 만약 null 혹은 undefined인 경우, undefined을 반환.

dog.tail이 null이나 undefined가 아닌 경우, softly 메소드(string값) 반환.

object 및 array 접근

object

type CustomResponse = {
  data: any
}

const findData = (response?: CustomResponse) => {
  return response?.data
}

response가 null이거나 undefined이면, undefined 반환

그렇지 않으면, data반환

array

type Post = {
  comments?: string[]
}

const gestFirstComment = (post: Post) => {
  return post.comments?.[0]
}

post.comments가 null이거나 undefined이면, undefined.

그렇지 않으면, 첫번째 요소 반환

함수 호출

type Dog = {
  tail?: {
    softly: () => string;
  }
}

function tailSoftly(dog: Dog): string {
  return dog.tail?.softly()
}  

optinal chaining으로 if문 줄여보기

// if문
type Tail = {
  살랑살랑: () => void;
};
type Human = {
  call: (dogName: string) => void;
};
type Dog = {
  name: string;
  tail?: Tail;
  주인?: Human;
};

function petDog(dog: Dog) {
  // != null: null 또는undefined가아닌경우
  if (dog.주인 != null) {
    dog.주인.call(dog.name);
  }
  if (dog.tail != null) {
    dog.tail.살랑살랑();
  }
}

const dog: Dog = {
  name: "elice",
  tail: {
    살랑살랑() {
      console.log("꼬리 살랑살랑~");
    },
  },
  주인: {
    call(dogName: string) {
      console.log("elice~");
    },
  },
};
// 위 if문을 optional chaining형식으로 변환
type Tail = {
  살랑살랑: () => void;
};
type Human = {
  call: (dogName: string) => void;
};
type Dog = {
  name: string;
  tail?: Tail;
  주인?: Human;
};

function petDog(dog: Dog) {
  dog.주인?.call(dog.name);  💙
  dog.tail?.살랑살랑();  💛
}

const dog: Dog = {
  name: "elice",
  tail: {
    살랑살랑() {
      console.log("꼬리 살랑살랑~");
    },
  },
  주인: {
    call(dogName: string) {
      console.log("elice~");
    },
  },
};

petDog(dog)
// elice~  💙
// 꼬리 살랑살랑~  💛

Null병합연산자(Nullish Coalescing Operator)

  • A ?? B
    • 좌항(A)이 null이거나 undefined이면 B반환
    • 그렇지 않으면 A반환
  • ||?? 비교
function getPrice(product: {
  price?: number 
  }) {
    return product.price || -1;
  }
}

product.price가 false, 0, ‘’ 등(falsy값)일 때 -1

→ price가 0인 경우 -1을 반환해버림

function getPrice(product: {
  price?: number 
  }) {
    return product.price ?? -1;
  }
}

product.price가 null, undefined일 때만 -1

→ price가 0인 경우 0 반환

Function Overloading

  • 파라미터의 형태가 다양한 여러 케이스에 대응하는 같은 이름을 가진 함수를 만드는 것
  • 오버로딩 선언 방식
    • 함수의 이름이 같아야 함
    • 매개변수의 순서는 서로 같아야 함
    • 반환타입이 같아야 함

예시

class User{
  constructor(private id: string) {}
  setId(id: string): void;
  setId(id: number): void;
  setId(id: string| number): void{
    this.id = typeof id === 'number' ? id.toString() : id;
  }
}
  • string| number : id가 string 혹은 number가 올 수 있기 때문에 union type으로 지정
  • typeof id === 'number' ? id.toString() : id; : id의 타입이 number이면 toString() 적용. 그렇지 않으면 id 그대로 출력

데코레이터


  • 함수가 한가지 일만 하면서 가독성을 높이고 반복되는 코드를 줄이기 위해 사용
    • 단일책임원칙 관련
  • 함수를 감싸하는 함수
  • 데코레이터를 사용하려면 tsconfig.json에서 experimentalDecorators옵션을 true로 설정
    // tsconfig.json
    {
      "compilerOptions": {
      "target": "ES5",
      "experimentalDecorators": true,
      "emitDecoratorMetadata": true
      }
    }
  • 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내려면 reflect-metadata 라이브러리 설치 yarn add reflect-metadata

예를 들어, 데코레이터 @sealed 를 사용하면 다음과 같이 sealed함수를 작성할 수 있음

function sealed(target) {
    // 'target' 변수와 함께 무언가를 수행합니다.
}

Decorator Factory

고차함수 → 함수를 반환하는 함수
팩토리 → 어떤 인스턴스를 생성하여 반환하는 함수(메서드)
데코레이터 팩토리 → 데코레이터 함수를 반환하는 함수

⇒ 데코레이터 팩토리는 고차함수의 일종으로 데코레이터 함수를 반환하는 함수이다.

⇒ 데코레이터 함수를 커스터마이징할 때 사용

function color(value: string) { // 데코레이터 팩토리
    return function (target) { // 데코레이터
        // 'target'과 'value' 변수를 가지고 무언가를 수행합니다.
  };
}
function decoratorFacotry(value: string) {
    return function(     // 함수를 반환 (이때 이 함수는 데코레이터 함수이다)
        target: any, 
        propertyKey: string, 
        propertyDescriptor: PropertyDescriptor
    ) {
        console.log(value)
    }
}

class ExampleClass {
  @decoratorFacotry("룰루랄라 퇴근이다")
  static method() {}
}

ExampleClass.method();  // 룰루랄라 퇴근이다

데코레이터 함수는 특정 인자만 받는다.

데코레이터에 추가적인 인자를 주고 싶다면 고차함수에 매개변수를 추가하여 데코레이터 함수 내부에서 사용할 수 있다.

데코레이터 합성

  • 선언에 여러 데코레이터를 적용하는 방법 → 수학의 합성합수와 유사
    • f(g(x))f(g(x))의 실행 순서: g(x)g(x)f(g(x))f(g(x))
  • 따라서 TypeScript에서 단일 선언에서 여러 데코레이터를 사용할 때 다음 단계가 수행됩니다.
    1. 각 데코레이터의 표현은 위에서 아래로 평가됩니다.
    2. 그런 다음 결과는 아래에서 위로 함수로 호출됩니다.
function first() {
  console.log("first(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("first(): called");
  };
}
 
function second() {
  console.log("second(): factory evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("second(): called");
  };
}
 
class ExampleClass {
  @first()
  @second()
  method() {
		console.log('method is called')
	}
}

<실행결과>

first(): factory evaluated
second(): factory evaluated
second(): called
first(): called
method is called

→ 위의 실행결과로 알 수 있듯이, 단순히 console.log으로 출력하면 위에서 아래로 실행되고,

함수의 형태로 return되는 순서는 아래에서 위로 실행됨

→ method 메서드는 맨 마지막에 출력

Class Decorator

  • 클래스 선언 직전에 선언

예시1

// 값을 반환하지 않는 데코레이터 팩토리 함수
function sealed(constructor: Function)
{
	Object.seal(constructor)
	Object.seal(constructor.prototype)
}

@sealed
class Post {
	constructor(public title: string) {}  // 그대로 사용
	publish(createdAt: Date) {
		console.log(`${this.title} is published at ${createdAt.toISOString()}`)
  }
}

값을 반환하지 않는 경우 클래스의 기존 생성자 함수를 그대로 사용

Object.seal : 객체가 가진 기존 property를 수정, 추가, 삭제 등의 변경은 가하지 못한다.

예시2 - BugReport클래스에 데코레이터를 이용해 reportingURL 속성을 새로 추가

function reportableClassDecorator<T extends{ new(...args: any[]): {} }>constructor: T) {
	return class extends constructor {   // 💚
		reportingURL = "http://www.example.com"  // 💛
	}
}

@reportableClassDecorator
class BugReport {
	type = "report"
	title: string
	constructor(t: string) {
		this.title = t
	}
}

const bug = new BugReport("Needs dark mode")
console.log(bug) 
// {type: 'report', title: 'Needs dark mode', reportingURL: 'http://example.com'}
  • 코드 설명
    • 1️⃣new(...args: any[]): {} : new 키워드와 함께 어떠한 형식의 인수들도 받을 수 있는 타입
    • <T extends{ 1️⃣ }>constructor: T) : 1️⃣을 상속받는 제네릭타입(T)을 가지는 생성자(constructor)를 인수로 전달
    • 💚 : 내가 정의한 클래스 데커레이터는 생성자를 리턴하는 함수
    • 💛 : 클래스 데커레이터가 적용되는 클래스에 새로운 reportingURL속성을 추가

Method Decorator

예시

functon deco(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
	console.log('데커레이터가 평가됨')
}

class TestClass {
	@deco
	test() {
		console.log('함수 호출')
	}
}

const t = new TestClass()
t.test()
// 데커레이터가 평가됨
// 함수 호출

만약 데커레이터에 인수를 넘겨서 데커레이터의 동작을 변경하고 싶다면, 데커레이터를 리턴하는 함수(데커레이터 팩터리)를 만들면 된다. ↓

functon deco(value: string) {
	console.log('데커레이터가 평가됨')
	return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
		console.log(value)
}

class TestClass {
	@deco('HELLO')
	test() {
		console.log('함수 호출')
	}
}

const t = new TestClass()
t.test()
// 데커레이터가 평가됨
// HELLO
// 함수 호출

나머지 데코레이터들은
📎 https://www.typescriptlang.org/ko/docs/handbook/decorators.html 참고
참고

 

※ 참고 : (도서) Nestjs으로 배우는 백엔드 프로그래밍

profile
안되어도 될 때까지

0개의 댓글