본문으로 바로가기

패키지 의존성 관리

package.json

package.json의 역할은 다음과 같습니다.

  • 애플리케이션이 필요로 하는 패키지 목록을 나열합니다.
  • 각 패키지는 시맨틱 버저닝 규칙으로 필요한 버전을 기술합니다.
  • 다른 개발자와 같은 빌드환경을 구성할 수 있습니다. 의존성이 달라 발생하는 문제를 예방합니다.

시맨틱 버저닝 규칙(Semantic Version Rules, SemVer)은 패키지의 버전명을 숫자로 관리하는 방법으로 많이 사용되고 있습니다.

버저닝 규칙은 다음과 같습니다.

[Major].[Minor].[Patch]-[label]

Major, Minor, Patch는 각각 숫자를 사용합니다. 예를 들어 1.2.3-beta와 같이 표기합니다.

  • Major: 이전 버전과 호환이 불가능할 때 숫자를 하나 증가시킵니다. Major 버전이 바뀐 패키지를 사용하고자 한다면 반드시 breaking change(하위 호환성이 깨진 기능) 목록을 확인하고 이전 기능을 사용하는 코드를 수정해야 합니다.
  • Minor: 기능이 추가되는 경우 숫자를 증가시킵니다. 기능이 추가되었다고 해서 이전 버전의 하위 호환성을 깨트리지는 않습니다.
  • Patch: 버그 수정 패치를 적용할 때 사용합니다.
  • label (선택사항): pre, alpah, beta와 같이 버전에 대해 부가 설명을 붙이고자 할 때 문자열로 작성합니다.

시맨틱 버저닝을 사용할 떄 완전히 동일한 버전만을 정의하지 않습니다. 다음과 같은 규칙으로 기술하여 의존성이 깨지지 않는 다른 버전을 설치할 수 있습니다.

  • ver: 완전히 일치하는 버전
  • =ver: 완전히 일치하는 버전
  • >ver: 큰 버전
  • >=ver: 크거나 같은 버전
  • <ver: 작은 버전
  • <=ver: 작거나 같은 버전
  • ~ver: 버전 범위
    • ~1.0, 1.0.x: 1.0 이상 1.1 미만의 버전 
  • ^ver: SemVer 규약을 따른다는 가정에서 동작하는 규칙
    • ^1.0.2: 1.0.2 이상 2.0 미만의 버전
    • ^1.0: 1.0.0 이상 2.0 미만의 버전
    • ^1: 1.0.0 이상 2.0 미만의 버전

 

package-lock.json

프로젝트 루트 디렉토리에서 npm install 명령을 수행하면 node_modules 디렉토리와 package-lock.json 파일이 생성됩니다.

그리고 package-lock.json 파일은 node_modules나 package.json 파일의 내용이 바뀌면 npm install 명령을 수행할 때 자동 수정됩니다.

node_modules는 프로젝트가 필요로 하는 패키지들이 실제로 설치되는 장소입니다. 애플리케이션은 런타임에 여기에 설치된 패키지들을 참조합니다. package-lock.json 파일은 package.json에 선언된 패키지들이 설치될 때의 정확한 버전과 서로간의 의존성을 표현합니다.

 

 

TypeScript

Nest는 타입스크립트를 기본 언어로 채택하고 있습니다. 자바스크립트로 설정을 바꿀 수도 있지만, 타입스크립트의 장점을 누리기 위해 기본 설정으로 사용하시기를 추천합니다.

타입스크립트는 마이크로소프트에서 개발한 언어입니다.

자바스크립트 코드에 타입 시스템을 도입하여 런타임 에러가 발생할 가능성이 있는 코드를 정적 분석으로 찾아줍니다.

타입스크립트는 자바스크립트에 구문을 추가하여 만들어졌습니다.

tsc 명령으로 컴파일하여 자바스크립트 코드로 변환이 가능합니다.

컴파일 후 생성된 자바스크립트는 타입이 없습니다. 자바스크립트는 원래 타입이 없기 때문입니다.

타입스크립트가 제공하는 타입 추론은 타입 오류로 인해 런타임에 발생하는 오류를 컴파일 타임에 잡습니다.

VSCode와 같은 IDE에서는 소스코드에 에러를 표시해주므로 일일히 컴파일 명령을 실행하지 않아도 됩니다.

 

*정적 분석(Static Analysis): 소스 코드를 실행하지 않고 코드만을 분석하여 문제가 될 부분을 찾아내는 기법

 

변수 선언

[선언키워드] [변수명]: [타입]
  • 선언키워드: const, let 또는 var로 선언합니다. const는 선언 후 재할당이 불가능하며, let과 var는 재할당이 가능하여 값을 바꿀 수 있습니다. let과 var의 차이는 호이스팅(hoisting) 여부인데, var는 변수를 사용한 후에 서넝닝 가능하지만 let은 그렇지 못합니다.

 

TypeScript에서 지원하는 타입

타입스크립트는 자바스크립트가 가지고 있는 자료형을 모두 포함합니다.

자바스크립트의 타입은 기본타입(Primitive value)과 객체형(Object), 함수형(Funtion)이 있습니다.

typeof 키워드를 이용하여 인스턴스의 타입을 알 수 있습니다.

typeof instance === 'undefined'

 

기본타입

typeof 설명 할당 가능한 값
boolean 참, 거짓을 나타내는 논리값 true, false
null "유효하지 않음"을 나타낸다. null
undefined 값이 존재하지 않음. 즉, 변수 선언 후 "값이 할당되지 않음"을 나타낸다. undefined
number 배정밀도 64비트 형식 IEEE 754의 값 -(2^53 -1)와 2^53 -1 사이의 정수와 실수
+infinity, infinity
NaN (Not a Number)
bigint Number의 범위를 넘어서는 정수를 안전하게 저장하고 연산할 수 있게 해준다. ex) const x = 2n ** 53n
정수 끝에 n을 추가한다
string 문자열. 변경 불가능(immutable)하다 홑따옴표 또는 쌍따옴표로 둘러싸인 문자열
ex) 'hello', "world"
symbol 유일하고 변경 불가능한(immutable) 기본값(primitive value)
객체 속성의 키로 사용할 수 있다.
 

 

객체타입

객체 타입은 속성(Property, 프로퍼티)을 가지고 있는 데이터 컬렉션입니다.

C언어의 구조체와 유사합니다.

속성은 키와 값으로 표현되는데 값은 다시 자바스크립트의 타입을 가지고 있습니다.

따라서 다음 예와 같은 데이터를 구조적으로 표현할 수 있습니다.

const dexter = {
  name: 'Dexter Kim',
  age: 26,
  hobby: ['Movie', 'Billiards'],
}

자바스크립트에는 개발할 때 유용한 내장객체들이 있습니다.

  • Date: 1970년 1월 1일 UTC 자정과의 시간 차이를 밀리초 단위로 나타낸 것으로 시간을 다룰 때 사용합니다.
  • 배열(Array): 정수를 키로 가지는 일련의 값을 가진 객체입니다. 코드로 표현할 때는 대괄호([ ])로 표현합니다.
  • 키를 가진 컬렉션: Map, WeakMap은 키와 값을 가지는 객체 타입이고, SetWeakSet은 유일값들로 이루어진 컬렉션 객체 타입입니다.
  • JSON: JSON(JavaScript Object Notation)은 자바스크립트에서 파생된 경량 데이터 교환 형식이지만, 많은 프로그래밍 언어에서 사용됩니다. JSON은 범용 데이터 구조를 구축합니다.

이 외 표준 라이브러리에는 더 많은 객체가 있습니다. MDN 문서를 참고하세요.

 

함수(Function) 타입

자바스크립트는 함수를 변수에 할당하거나 다른 함수의 인자로 전달할 수 있습니다.

함수의 결과로 반환할 수도 있습니다.

언어의 이러한 특징을 일급 함수라고 합니다.

함수 func의 타입을 검사하면 'function'이 됩니다.

typeof func === 'function'

 

any / unknown / never

타입스크립트에는 특수한 타입이 있습니다.

any는 자바스크립트와 같이 어떠한 타입의 값도 받을 수 있는 타입니다.

any 타입의 객체 역시 어떤 타입의 변수에도 할당이 가능합니다.

이 특성때문에 런타임에 오류를 일으킬 가능성이 있습니다.

 

unknown 타입은 any타입과 마찬가지로 어떤 타입도 할당 가능하지만, 다른 변수에 할당 또는 사용할 때 타입을 강제하도록 하여

any가 일으키는 오류를 줄여줍니다.

 

never 타입의 변수에는 어떤 값도 할당할 수 없습니다.

함수의 리턴 타입으로 지정하면 함수가 어떤 값도 반환하지 않는다는 것을 뜻하고,

다음과 같이 특정 타입의 값을 할당받지 못하도록 하는데 사용할 수도 있습니다.

type NonString<T> = T extends string ? never : T

* <T> 는 제네릭 타입

 

 

타입 정의하기

타입스크립트는 타입을 정의해서 사용할 수 있습니다.

기본타입과 같은 타입을 정의한다는 뜻은 아니고, 위에서 설명한 타입들을 조합하여 타입에 이름을 붙여 사용합니다.

다음 코드를 VSCode에 입력하여 마우스를 user 변수 위로 가져가 보세요.

const user = {
  name: 'Dexter',
  age: 26,
}

추론된 타입이 다음과 같이 표시됩니다.

const use: {
  name: string;
  age: number;
}

변수에 객체를 바로 할당하지 않고 interface로 정의할 수 있습니다.

인터페이스를 이용하여 User 타입을 선언하는 것입니다.

interface User {
  name: string;
  age: number;
}

const user: User = {
  name: 'Dexter',
  age: 26,
}

interface는 class로 선언할 수도 있습니다.

class User {
  constructor(name: string, age: number) { }
}

const user: User = new User('Dexter', 26)
생성자에 선언된 변수는 클래스 멤버변수가 됩니다.
접근제한자(public, private)가 없으면 public 변수가 됩니다.
멤버변수를 사용할 때는 this.name과 같이 this 키워드와 함께 사용합니다.

또 타입은 type 키워드로 새로운 타입을 만들 수  있습니다.

type MyUser = User

MyUser 타입은 기존 User 타입을 그대로 사용하지만 내가 사용하는 도메인에 맞는 이름으로 바꾼 것입니다.

 

타입 구성하기

자바스크립트는 변수에 어떠한 타입의 값도 할당할 수 있습니다.

일명 덕 타이핑이라 부릅니다.

타입스크립트도 여러 타입의 값을 할당할 수 있습니다.

여러 타입을 조합한 새로운 타입을 가지는 것입니다.

 

* 덕 타이핑(duck typing): 객체의 변수 및 메서드의 집합이 객체의 타입을 결정하는 것을 말한다. 클래스 상속이나 인터페이스 구현으로 타입을 구분하는 대신, 객체가 어떤 타입에 걸맞은 변수와 메서드를 지니면 객체를 해당 타입에 속하는 것으로 간주한다.

 

유니언(Union) 타입

유니언 타입이 위에서 설명했던 여러 타입을 조합한 타입입니다.

다음 코드에서 getLength 함수의 인자인 obj는 string 또는 string 배열 타입을 가질 수 있습니다.

function getLength(obj: string | string[]) {
  return obj.length
}

유니언 타입을 활용하면 변수가 가질 수 있는 값을 제한할 수도 있습니다.

type Status = "Ready" | "Waiting"
타입스크립트는 열거형을 제공합니다. 위 코드는 열거형으로 사용하는 게 더 편합니다.
enum Status {
  READY: "Ready",
  WAITING: "Waiting",
}

 

제네릭(Generic) 타입

Java나 C#에서의 제네릭과 유사한 기능을 합니다.

어떠한 타입이든 정의될 수 있지만 호출되는 시점에 타입이 결정됩니다.

만약 다음과 같이 인자를 그대로 리턴하는 함수가 있다고 합시다.

function identity(arg: any): any {
  return arg;
}

이 함수의 반환값은 any로 되어 있기 때문에 arg에 'test'를 인자로 전달할 경우 인자의 string 타입이 변환할 때 any가 되어 버립니다.

반면 다음과 같이 제네릭 타입을 사용하게 되면 리턴되는 값의 타입은 함수를 호출하는 시점의 인자로 넣은 타입으로 결정되도록 할 수 있습니다.

function identity<T>(arg: T): T {
  return arg;
}

제네릭을 선언할 때는 보통 대문자 한글자를 사용합니다.

 

 

데코레이터

Nest는 데코레이터를 적극 활용합니다.

데코레이터를 잘 사용하면 횡단관심사를 분리하여 관점 지향 프로그래밍을 적용한 코드를 작성할 수 있습니다.

타입스크립트의 데코레이터는 파이썬의 데코레이터나 자바의 어노테이션과 유사한 기능을 말합니다.

클래스, 메서드, 접근자, 프로퍼티, 매개변수에 적용 가능합니다.

각 요소의 선언부 앞에 @로 시작하는 데코레이터를 선언하면 데코레이터로 구현된 코드를 함께 실행합니다.

예를 들어 다음 코드는 유저 생성 요청의 본문을 DTO로 표현한 클래스입니다.

class CreateUserDto {
  @IsEmail()
  @MaxLength(60)
  readonly email: string;
  
  @IsString()
  @Matches(/^[A-Za-z\d!@#$%^&*()]{8,30}$/)
  readonly password: string;
}

사용자는 얼마든지 요청을 잘못 보낼 수 있기 때문에 데코레이터를 이용하여 애플리케이션이 허용하는 값으로 제대로 요청을 보냈는지 검사하고 있습니다.

email은 이메일 형식을 가진 문자열이어야 하고(@IsEmail()) 그 길이는 최대 60자이어야 합니다(@MaxLength(60)).

password는 문자열이어야 하고(@IsString()) 주어진 정규 표현식에 적합해야 합니다(@Matches(...)).

 

데코레이터는 타입스크립트 스펙에서 아직 실험적인 기능입니다. 

tsconfig.json 파일을 보겠습니다. tsconfig.json 파일은 타입스크립트의 빌드환경을 정의한 파일입니다.

{
  "compilerOptions": {
  	...
    "experimentalDecorators": true,
    	...
  }
}

experimentalDecorators 옵션이 true로 설정되어 있습니다. 이 옵션을 켜야 데코레이터를 사용할 수 있습니다. 비록 실험적인 기능이지만 매우 안정적이며, 수많은 프로젝트에서 사용하고 있습니다.

 

데코레이터는 위에서 봤던 것처럼 @expression과 같은 형식으로 사용합니다.

여기서 expression은 데코레이팅 된 선언(데코레이터가 선온되는 클래스, 메서드 등)에 대한 정보와 함께 런타임에 호출되는 함수여야합니다.

다음과 같은 메서드 데코레이터가 있고, 이 데코레이터를 test라는 메서드에 선언했습니다.

여기서 deco 함수에 인자들이 있는데, 메서드 데코레이터로 사용하기 위해서는 이렇게 정의해야 합니다.

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

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

const t = new TestClass();
t.test();

이제 TestClass를 생성하고, test 메서드를 호출하면 다음과 같은 결과가 콘솔에 출력됩니다.

데코레이터가 평가됨
함수 호출됨

만약 데코레이터에 인자를 넘겨서 데코레이터의 동작을 변경하고 싶다면 데코레이터 팩토리, 즉 데코레이터를 리턴하는 함수를 만들면 됩니다. 위의 예시를 다음과 같이 value라는 인자를 받도록 바꿔보겠습니다.

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

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

결과는 다음과 같습니다.

데코레이터가 평가됨
HELLO
함수 호출됨

 

 

데코레이터 합성

만약 여러 개의 데코레이터를 사용한다면, 수학에서의 함수 합성과 같이 적용됩니다.

다음 데코레이터 선언의 합성 결과는 f(g(x))와 같습니다.

@f
@g
test

여러 데코레이터를 사용할 때 다음 단계가 수행됩니다.

  1. 각 데코레이터의 표현은 위에서 아래로 평가(evaluate)됩니다.
  2. 그런 당므 결과는 아래에서 위로 함수로 호출(call)됩니다.

다음 예의 출력 결과를 보면 합성 순서에 대해 이해를 높일 수 있을 것입니다.

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

 

 

클래스 데코레이터 (Class Decorator)

클래스 데코레이터는 클래스 바로 앞에 선언됩니다.

클래스 데코레이터는 클래스의 생성자에 적용되어 클래스 정의(definition)를 읽거나 수정할 수 있습니다.

선언 파일과 선언 클래스(declare class)내에서는 사용할 수 없습니다.

 

다음 코드는 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);

L1 = Line 1

  • L1: 클래스 데코레이터 팩토리입니다. 생성자 타입(new (...args: any[]): {}. new 키워드와 함께 어떠한 형식의 인자들도 받을 수 있는 타입)을 상속받는 제네릭 타입 T를 가지는 생성자(constructor)를 팩토리 메서드의 인자로 전달하고 있습니다.
  • L2: 클래스 데코레이터는 생성자를 리턴하는 함수여야 합니다.
  • L3: 클래스 데코레이터가 적용되는 클래스에 새로운 reportingURL이라는 새로운 속성을 추가합니다.

위 코드의 출력 결과는 다음과 같습니다.

{type: 'report', title: 'Needs dark mode', reportingURL: 'http://www.example.com'}

BugReport 클래스에 선언되어 있지 않은 새로운 속성이 추가되었습니다.

⚠️ 클래스의 타입이 변경되는 것은 아닙니다. 타입 시스템은 reportingURL을 인식하지 못하기 떄문에 bug.reportingURL과 같이 직접 사용할 수 없습니다.

 

 

메서드 데코레이터 (Method Decorator)

메서드 데코레이터는 메서드 바로 앞에 선언됩니다.

메서드의 속성 디스크립터(Property Descriptor, 속성의 특성을 설명하는 객체)에 적용되고 메서드의 정의를 읽거나 수정할 수 있습니다.

선언 파일, 오버로드 메서드, 선언 클래스에 사용할 수 없습니다.

 

앞서 deco 메서드 데코레이터에서 보았던 것처럼 메서드 데코레이터는 다음 세 개의 인수를 가집니다.

  1. 정적 멤버가 속한 클래스의 생성자 함수이거나, 인스턴스 멤버에 대한 클래스의 프로토타입
  2. 멤버의 이름
  3. 멤버의 속성 디스크립터. PropertyDescriptor 타입을 가짐.

만약 메서드 데코레이터가 값을 반환한다면 이는 해당 메서드의 속성 디스크립터가 됩니다.

메서드 데코레이터의 예를 보겠습니다.

함수를 실행하는 과정에서 에러가 발생했을 때, 이 에러를 잡아서 처리하는 로직을 구현하고 있습니다.

function HandleError() {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log(target)
    console.log(propertyKey)
    console.log(descriptor)

    const method = descriptor.value;

    descriptor.value = function() {
      try {
        method();
      } catch (e) {
        // 에러 핸들링 로직 구현
        console.log(e);
      }
    }
  };
}

class Greeter {
  @HandleError()
  hello() {
    throw new Error('테스트 에러');
  }
}

const t = new Greeter();
t.hello();
  • L2: 메서드 데코레이터가 가져야 하는 3개의 인자입니다. ProperyDescriptor는 객체 속성의 특성을 기술하고 있는 객체로써 enumerable 외에도 여러가지 속성을 가지고 있습니다. enumerable이 true가 되면 이 속성을 열거형이라는 뜻이 됩니다.
interface PropertyDescriptor {
  configurable?: boolean;  // 속성의 정의를 수정할 수 있는지 여부
  enumerable?: boolean;    // 열거형인지 여부
  value?: any;             // 속성 값
  writable?: boolean;      // 수정 가능 여부
  get?(): any;             // getter
  set?(v: any): void;      // setter
}
  • L3: 출력 결과는 {constructor: ƒ, greet: ƒ} 입니다. 데코레이터가 선언된 메서드 hello가 속해있는 클래스의 생성자와 프로토타입을 가지는 객체임을 알 수 있습니다.
  • L4: 함수 이름 hello가 출력됩니다.
  • L5: hello 함수가 처음 가지고 있던 디스크립터가 출력됩니다. 출력결과는 {value: ƒ, writable: true, enumerable: false, configurable: true} 입니다.
  • L7: 디스크립터의 value 속성으로 원래 정의된 메서드를 저장합니다.
  • L10: 원래 메서드를 호출합니다.
  • L12: 만약 원래 메서드를 수행하는 과정에서 발생한 에러를 핸들링하는 로직을 이 곳에 구현합니다.
  • L13: Error: 테스트 에러가 출력됩니다.

 

접근자 데코레이터 (Accessor Decorator)

접근자 데코레이터는 접근자 바로 앞에 선언합니다.

접근자의 속성 디스크립터에 적용되고 접근자의 정의를 읽거나 수정할 수 있습니다.

역시 선언 파일과 선언 클래스에 사용할 수 없습니다.

접근자 데코레이터가 반환하는 값은 해당 멤버의 속성 디스크립터가 됩니다.

 

* 접근자: 객체 프로퍼티를 객체 외에서 읽고 쓸 수 있는 함수. 쉽게 이야기해서 게터(getter)와 세터(setter). 타입스크립트에는 게터와 세터를 구현할 수 있는 getset 키워드가 있다.

 

특정 멤버를 열거나 가능한 지 결정하는 데코레이터의 예를 보겠습니다.

function Enumerable(enumerable: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = enumerable;
  }
}

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

  @Enumerable(true)
  get getName() {
    return this.name;
  }

  @Enumerable(false)
  set setName(name: string) {
    this.name = name;
  }
}

const person = new Person('Dexter');
for (let key in person) {
  console.log(`${key}: ${person[key]}`);
}
  • L3: 디스크립터의 enumerable 속성을 데코레이터의 인자로 결정합니다.
  • L8: name은 외부에서 접근하지 못하는 private 멤버입니다.
  • L10~13: 게터 getName 함수는 열거가 가능하도록 합니다.
  • L15~16: 세터 setName 함수는 열거가 불가능하도록 합니다.
  • L21~24: 결과를 출력하면 getName은 출력되지만, setName은 열거하지 못하게 되었기 때문에 for문에서 key로 받을 수가 없습니다.
name: Dexter
getName: Dexter

 

 

속성 데코레이터 (Property Decorator)

속성 데코레이터는 클래스의 속성 바로 앞에 선언됩니다.

역시 선언 파일, 선언 클래스에서 사용하지 못합니다.

속성 데코레이터는 다음 두 개의 인수를 가지는 함수입니다.

  1. 정적 멤버가 속한 클래스의 생성자 함수이거나 인스턴스 멤버에 대한 클래스의 프로토타입
  2. 멤버의 이름

메서드 데코레이터나 접근자 데코레이터와 비교했을 때, 세 번째 인자인 속성 디스크립터가 존재하지 않습니다.

공식문서에 따르면 반환값도 무시되고, 이는 현재 프로토타입(prototype)의 멤버를 정의할 때 인스턴스 속성을 설명하는 매커니즘이 없고 속성의 초기화 과정을 관찰하거나 수정할 수 없는 방법이 없기 때문이라고 합니다.

function format(formatString: string) {
  return function (target: any, propertyKey: string): any {
    let value = target[propertyKey];

    function getter() {
      return `${formatString} ${value}`;
    }

    function setter(newVal: string) {
      value = newVal;
    }

    return {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true,
    }
  }
}

class Greeter {
  @format('Hello')
  greeting: string;
}

const t = new Greeter();
t.greeting = 'World';
console.log(t.greeting);
  • L6: 게터에서 데코레이터 인자로 들어온 formatString을 원래의 속성과 조합한 스트링으로 바꿉니다.
  • L23: 데코레이터에 formatString을 전달합니다.
  • L29: 속성을 읽을 때 게터가 호출되면서 Hello World가 출력됩ㄴ디ㅏ.

 

 

매개변수 데코레이터 (Parameter Decorator)

생성자 또는 메서드의 파라미터에 선언되어 적용됩니다.

선언파일, 선언 클래스에서 사용할 수 없습니다.

매개변수 데코레이터는 호출 될 때 3가지의 인자와 함께 호출됩니다. 반환값은 무시됩니다.

  1. 정적 멤버가 속한 클래스의 생성자 함숭거나 인스턴스 멤버에 대한 클래스의 프로토타입
  2. 멤버의 이름
  3. 매개변수가 함수에서 몇 번째 위치에 선언되었는 지를 나타내는 인덱스

파라미터가 제대로 된 값으로 전달되었는지 검사하는 데코레이터를 만들어 보겠습니다.

매개변수 데코레이터는 단독으로 사용하는 것보다 함수 데코레이터와 함께 사용할 때 유용하게 쓰입니다.

Nest에서 API 요청 파라미터에 대해 유효성 검사를 할 때 이와 유사한 데코레이터를 많이 사용합니다.
import { BadRequestException } from '@nestjs/common';

function MinLength(min: number) {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    target.validators = {
      minLength: function (args: string[]) {
        return args[parameterIndex].length >= min;
      }
    }
  }
}

function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const method = descriptor.value;

  descriptor.value = function(...args) {
    Object.keys(target.validators).forEach(key => {
      if (!target.validators[key](args)) {
        throw new BadRequestException();
      }
    })
    method.apply(this, args);
  }
}

class User {
  private name: string;

  @Validate
  setName(@MinLength(3) name: string) {
    this.name = name;
  }
}

const t = new User();
t.setName('Dexter');
console.log('----------')
t.setName('De');
  • L3: 파라미터의 최소값을 검사하는 파라미터 데코레이터
  • L5~9: target 클래스(여기서는 User)의 validators 속성에 유효성을 검사하는 함수를 할당합니다.
  • L6: args 인자는 18번 라인에서 넘겨받은 메서드의 인자입니다.
  • L7: 유효성 검사를 위한 로직입니다. parameterIndex에 위치한 인자의 길이가 최소값보다 같거나 큰지 검사합니다.
  • L13: 함께 사용할 메서드 데코레이터
  • L14: 메서드 데코레이터가 선언된 메서드를 method 변수에 임시 저장해 둡니다.
  • L16: 디스크립터의 value에 유효성 검사 로직이 추가된 함수를 할당합니다.
  • L17~21: target(User 클래스)에 저장해 둔 validators를 모두 수행합니다. 이때 원래 메서드에 전달된 인자(args)들을 각 validator에 전달합니다.
  • L22: 원래의 함수를 실행합니다.
  • L36: 파라미터 name의 길이가 5이기 때문에 문제가 없습니다.
  • L38: 파라미터 name의 길이가 3보다 작기 때문에 BadRequestException이 발생합니다.

지금까지 알아본 5가지 데코레이터를 자세히 알아보았습니다. 각 데코레이터의 특징을 간략히 정리하면 다음과 같습니다.

데코레이터 역할 호출시 전달되는 인자 선언 불가능한 위치
클래스 데코레이터 클래스의 정의를 읽거나 수정 (constructor) d.ts 파일, declare 클래스
메서드 데코레이터 메서드의 정의를 읽거나 수정 (target, propertyKey, propertyDescriptor) d.ts 파일, declare 클래스,
오버로드 메서드
접근자 데코레이터 접근자의 정의를 읽거나 수정 (target, propertyKey, propertyDescriptor) d.ts 파일, declare 클래스
속성 데코레이터 속성의 정의를 읽음 (target, propertyKey) d.ts 파일, declare 클래스
매개변수 데코레이터 매개변수의 정의를 읽음 (target, propertyKey, parameterIndex) d.ts 파일, declare 클래스

 

참고: NestJS로 배우는 백엔드 프로그래밍

반응형