느슨한 결합 & 의존성 주입(DI), NestJS / TypeScript

류연찬·2023년 5월 17일
0

GraphQL

목록 보기
9/17

Loose-Cooupling & Dependency Injection

느슨한 결합(Loose-Coupling)

SW에서 결합을 "둘 이상의 객체"로 해석했을 때, 느슨한 결합(Loose-Coupling)이란 "객체들 간에 결합이 되어있긴 하는데 헐겁게 됐다." 로 해석할 수 있습니다.

느슨한 결합(Loose-Coupling)은 다른 클래스를 직접적으로 사용하는 클래스의 의존성을 줄인은 것이고 강한 결합(Tight Coupling)은 클래스와 객체가 서로 의존하고 있는 것입니다.

이전에 설명했던 강한 결합(Tight-Coupling)의 반대 개념이라고 생각하면 됩니다.

강한 결합(Tight-Couplin)의 특징

  • 하나의 객체를 변경하게 되면 다른 객체들을 변경을 요구되어 변경점들을 확인하고 쉽게 놓칠 수 있습니다
  • 결합이 강하게 되어있어 결합이 되어있지 않으면 사용을 할 수 없게 됩니다
  • new 를 선언할 때마다 컴퓨터 메모리를 사용하는데 비교적으로 강한 결합에서 new 를 더 많이 사용해 메모리를 많이 잡아먹게 됩니다

느슨한 결합(Loose-Coupling)의 특징

  • 클래스/클래스를 느슨하게 결합되어 새로운 기능을 개발하거나 기존 기능을 수정하고 확장하는게 쉽습니다
  • 코드의 유지 보수가 쉽습니다
  • 테스트 대역으로 치환하기가 쉬워 유닛 테스트가 용이합니다

IoC(Inversion of Control / 제어의 역전)

제어의 역전(Iversion of Control)은 일반적인 디자인 패턴 중 하나입니다.

일반적으로 개발자가 프로그램의 흐름을 제어하는 주체였다면, IoC의 개념이 나오게 되며 프레임워크가 dependency를 container화 시켜 생명주기를 관리하게 되었습니다.

즉, dependency의 제어권이 개발자에서 프레임워크로 넘어가게 되었으며 이를 제어권의 흐름이 변경되었더고 하여 IoC(Inversion of Control)이라고 합니다.

DI(Dependency Injection / 의존성 주입)

DI(Dependency Injection / 의존성 주입)는 강한 결합(Tight-Coupling)을 느슨한 결합(Loose-Coupling)으로 전환시키는 방버이며, 제어의 역전(Inversion of Control)의 기술 중 하나입니다.

  • 제어의 역전 : "내가 대신 제어해줄께"
  • 의존성 주입 : "너가 정의한 코드(클래스, 변수 등등)를 ..."

Loose-Coupling & DI 실습 01

11-06-mvc-tight-coupling-with-product-coupon 폴더를 복사하여 사본을 만들고 폴더명을 12-01-mvc-di-singleton-ioc-1 으로 변경해주세요.

product.controller.js 파일을 수정해주세요.

export class ProductController {
    constructor (moneyService, productService) {
        this.moneyService = moneyService
        this.productService = productService
    }

    buyProduct(_, res) {
        // 1. 가진 돈 검증하는 코드
        const hasMoney = this.moneyService.checkValue() // true or false

        // 2. 판매여부 검증하는 코드
        const isSoldout = this.productService.checkSoldout() // true or false

        // 3. 상품 구매하는 코드
        if(hasMoney && !isSoldout) {
            res.send('상품을 구매합니다.')
        }
    }

    refundProduct(_, res) {
        // 1. 판매여부 검증하는 코드
        const isSoldout = this.productService.checkSoldout() // true of false

        // 2. 상품 환불하는 코드
        if(isSoldout) {
            res.send('상품을 환불합니다.')
        }
    }
}

Constructor Inject(생성자 주입) 을 사용해서 DI(Dependency Injection - 의존성 주입) 를 해주었습니다.

constructor 를 사용해서 DI를 해주었기 때문에, 제어가 역전이 되었습니다.

이전에 강한 결합을 했을때는 모듈을 불러와야했지만 느슨한 결합은 모듈을 불러오지 않아도 됩니다.

export class CouponController {
    constructor (moneyService) {
        this.moneyService = moneyService
    }

    buyCoupon(_, res) {
        // 1. 가진 돈 검증하는 코드
        const hasMoney = this.moneyService.checkValue() // true or false

        // 2. 쿠폰 구매하는 코드
        if(hasMoney) {
            res.send('쿠폰을 구매합니다.')
        }
    }
}

coupon.controllers.js 파일을 위와 같이 수정해주세요.

import express from "express";
import { ProductController } from './mvc/controllers/product.controllers.js'
import { CouponController } from './mvc/controllers/coupon.controllers.js'
import { CashService } from './mvc/controllers/services/cash.service.js'
import { ProductService } from './mvc/controllers/services/product.service.js'

const app = express()

const moneyService = new CashService()  // 싱글톤 패턴
const productService = new ProductService()

// 쿠폰 API
const couponController = new CouponController(moneyService)
app.post('coupon/buy', couponController.buyCoupon)

// 상품 API
const productController = new ProductController(moneyService, productService)
app.post('/product/buy', productController.buyProduct)
app.post('/product/refund', productController.refundProduct)

app.listen(3000)

요청을 핸들링 하는 index.js 를 다음과 같이 수정해주세요.

index.jsSingleton Pattern(싱글톤 패턴)을 적용했습니다.

Singleton Pattern(싱글톤 패턴)

비즈니스 로직은 CashServiceProductService 를 먼저 선언하였습니다.

이렇게하면 new 한번으로 보든 곳에서 사용 가능합니다.

이러한 디자인 패턴을 Singleton Pattern(싱글톤 패턴)이라고 합니다.

Loose-Coupling & DI 실습 02

12-01-mvc-di-singleton-ioc-1 폴더를 복사하여 사본을 생성하고 폴더명을 12-02-mvc-di-singleton-ioc-2 으로 변경해줍니다.

이번에는 포인트 결제를 추가해보겠습니다.

/mvc/controllers/services/point.service.js경로에 point.service.js 파일을 생성해줍니다.

그리고 다음과 같이 코드를 작성해줍니다.

export class PointService {
    checkValue() {
        // 1. 가진 포인트를 검증하는 코드
    }
}

index.js 파일을 다음과 같이 수정해주세요.

import express from "express";
import { ProductController } from './mvc/controllers/product.controllers.js'
import { CouponController } from './mvc/controllers/coupon.controllers.js'
import { CashService } from './mvc/controllers/services/cash.service.js'
import { ProductService } from './mvc/controllers/services/product.service.js'
import { PointService } from './mvc/controllers/services/point.service.js'

const app = express()

const moneyService1 = new PointService() // 포인트 결제 추가
const moneyService2 = new CashService()  // 싱글톤 패턴
const productService = new ProductService() // new 한번으로 모든 곳에서 사용 가능

// 쿠폰 API
const couponController = new CouponController(moneyService1)
app.post('coupon/buy', couponController.buyCoupon)

// 상품 API
const productController = new ProductController(moneyService2, productService)
app.post('/product/buy', productController.buyProduct)
app.post('/product/refund', productController.refundProduct)

app.listen(3000)

const moneyService1 = new PointService() 를 추가했습니다.

만약 ProductController에서 포인트 결제 기능을 사용하고 싶다면, new 생성자에게 매개 변수만 변경해주면 간편하게 관리 수정이 가능하게 됩니다.

Loose-Coupling & DI 실습 03

12-02-mvc-di-singleton-ioc-2 폴더를 복사하여 사본을 생성하고 폴더명을 12-03-mvc-di-singleton-ioc-with-this 으로 변경해주세요.

터미널에서 해당 폴더로 이동해 서버를 실행시켜주세요.

export class CouponController {
    constructor (moneyService) {
        this.moneyService = moneyService
    }

    buyCoupon(_, res) {
        // 1. 가진 돈 검증하는 코드
        const hasMoney = this.moneyService.checkValue() // true or false

        // 2. 쿠폰 구매하는 코드
        if(hasMoney) {
            res.send('쿠폰을 구매합니다.')
        }
    }
}

coupon.controllers.js 파일을 보게 되면 분명 this.moneyService.checkValue() 로 가진 돈을 검증하게 해놓았지만 읽지 못하고 있습니다.



이유는 선언식 함수화살표 함수(() => {})의 차이 때문입니다.

화살표 함수를 사용하면 this는 함수를 선언할 때 상위 스코프를 가리킵니다.

일반 함수를 사용하면, 함수를 호출한 객체로 this가 바인딩이 되기 때문에 읽지 못하게 됩니다.

따라서 화살표 함수르 사용하게 되면 바로 최상단에 생성자 주입해 놓은 this.moneyService 를 찾습니다.

💡 this 참고 자료 : https://poiemaweb.com/js-this


controller에 작성해 놓은 일반 함수를 모두 화살표 함수로 변경해주세요.

// product.controllers.js

export class ProductController {
    constructor (moneyService, productService) {
        this.moneyService = moneyService
        this.productService = productService
    }

    buyProduct = (_, res) => {
        // 1. 가진 돈 검증하는 코드
        const hasMoney = this.moneyService.checkValue() // true or false

        // 2. 판매여부 검증하는 코드
        const isSoldout = this.productService.checkSoldout() // true or false

        // 3. 상품 구매하는 코드
        if(hasMoney && !isSoldout) {
            res.send('상품을 구매합니다.')
        }
    }

    refundProduct = (_, res) => {
        // 1. 판매여부 검증하는 코드
        const isSoldout = this.productService.checkSoldout() // true of false

        // 2. 상품 환불하는 코드
        if(isSoldout) {
            res.send('상품을 환불합니다.')
        }
    }
}
// coupon.controllers.js

export class CouponController {
    constructor (moneyService) {
        this.moneyService = moneyService
    }

    buyCoupon = (_, res) => {
        // 1. 가진 돈 검증하는 코드
        const hasMoney = this.moneyService.checkValue() // true or false

        // 2. 쿠폰 구매하는 코드
        if(hasMoney) {
            res.send('쿠폰을 구매합니다.')
        }
    }
}

postman을 열어서 localhost:3000/coupon/buyPOST 요청을 보내주세요.

화살표 함수로 수정하니 정상적으로 작동합니다.

NestJS

NestJS?

NestJS란 TypeScript를 지원하는 효율적이고 확장가능한 Node.js의 서버 애플리케이션 프레임워크이며, OOP(Object Oriented Programming), FP(Functional Programming) 및 FRP(Functional Reactive Programming) 요소를 결합하는 특지을 가지고 있습니다.

Why is NestJS?

공식문서에 따르면, 최근 몇 년간 Node.js 덕분에 Javascript는 프론트, 백엔드 모두를 위한 웹의 "링구아 프랑카"가 되었고 이로 인해 Vue, React, Angular와 같은 프로젝트가 생겨 개발자의 생산성이 향상되고 빠르고, 테스트가 가능하며 확장성이 가능한 Fronted Appliaction을 만들 수 있습니다.

그러나 Node와 Server측 Javascript를 위한 훌륭한 모듈, 라이브러리들이 존재해도 Architecture의 주요 문제를 해결하지 못했습니다.

Node.js로 Backend를 만드는 것은 마치 레고와 같습니다.

레고처럼 조립할 수 있는 부품이 있고, 그 작은 부품을 차근차근 조립하다보면 거대한 완성품을 만들 수 있습니다.

Node.js도 마찬가지로 하나의 파일에서 시작해서 점차 커지게 됩니다.

규칙도, 제약도 없이 자유롭게 할 수 있고 0에서 거대한 것을 창조하는 과정이기에 매우 훌륭한 경험이 될 수 있습니다.

문제는 너무 제약이 없고, 너무 자유롭다는 것입니다.

떄로는 규칙이나 제약, 즉 구조와 프레임워크가 필요할 때가 있습니다.

다른 언어, 예를 들어 파이썬에는 Django 라는 프레임워크가 있고 Java는 Spring 이 있습니다.

하지만 Node.js에는 규칙이 없고 어떤 패턴을 따라야할지도 모르며 사용자가 정의해서 사용해야 합니다.

이런 자유로움은 좋은 점도 많지만, 프로젝트로 협업을 진행할 경우 매우 어렵습니다.

개발자마다 다양한 Architecture 패턴을 가지고 있고, 프로젝트의 규모가 커질수록 개인의 구조와 스타일의 다름으로 인해 협업 과정에 드는 소통 비용이 증가합니다.

이는 생산성 저하와 유지 보수의 어려움으로 이어집니다.

아키텍쳐(Architecture)

NestJS는 Architecture 구조를 제공함으로써 Node.js의 주요 문제를 해결해줍니다.

NestJS는 Node.js를 위한 프레임워크로, 규칙과 구조없이 자유분방한 node.js를 순식간에 Python+Django, Java+Spring 수준으로 만들어줍니다.

때문에 각 개발자들이 Architecture를 통일하고 소통 비용을 절감하며, 확장성 있고 효육적인 개발을 할 수 있습니다.

효율성

NestJS는 개발자와 팀이 고도로 테스트 가능하고, 확장 가능하며, 느슨하게 결합되고 유지관리가 쉬운 애플리케이션을 만들 수 있는 즉시 사용가능한 애플리케이션 아키텍쳐를 제공합니다.

그 외에도 TypeScript기반의 Framework이며 Dependency Injection(의존성 주입), Inversion of Control(제어의 역전), Module**을 통한 구조화 등 생산성에 용이합니다.

안정성

NestJS는 TypeScript를 적극적으로 도입하면서 서버 애플리케이션 개발 시 발생가능한 오류들을 사전에 방지할 수 있도록 합니다.

또한 세부적인 Module로 나누어져 있기 때문에 독립적인 Unit Test를 쉽게 작성 가능하도록 구현되어 있습니다.

확장성

NestJS는 Module Class를 지원하며, 각 Module은 비슷한 기능과 개념들을 Class 한 곳에 담아 캡슐화하고 서로 import가 가능하도록 구현되어 있습니다.

이러한 Modeul 구조는 Architecture를 조직적(Organize)으로 가져가게 하고 느슨한 결합(Loose-Coupling)을 가능하게 만들어 확정상(Extensible)테스트 가능성(Testable)을 높입니다.

Install NestJS

NestJS 공식 홈페이지에 접속하여 설치합니다.

옆 목차에서 First steps를 클릭하면 Setup에 대한 안내가 나와있습니다.

공식문서를 참고해 설치해보겠습니다.

터미널을 실행시켜 아래와 같은 명령어로 설치합니다.

npm i -g @nestjs/cli
or
yarn global add @nestjs/cli



permission 에러가 날 경우에 명령어 앞에 sudo 를 붙여 관리자 권한으로 설치합니다.

💡 CLI?
평소 우리가 컴퓨터에서 폴더 아이콘을 클릭해 들어가 안에 있는 파일들을 아이콘으로 보고, 또 그것들을 클릭해서 실행합니다. 이것들을 GUI, Graphical User Interface라고합니다.

CLICommand-Line Interface로 방금 한 것처럼 명령어를 입력해 폴더를 열고, 파일을 실행하는 등의 상호 작용하는 방식을 말합니다.

nestjs/cli 를 설치하면 nest 명령어를 사용할 수 있게 됩니다.


설치가 완료되면 nest -version 으로 버전을 확인합니다.

설치가 잘 안되었으면 버전이 뜨지 않습니다.

이제 nest 명령어를 써서 새로운 nest 프로젝트를 만들겠습니다.

12-04-nestjs 프로젝트를 생성해주시고 아래의 명령어를 이용해 프로젝트를 생성합니다.

nest new 12-04-nestjs

키보드 화살표를 움직여 yarn 으로 이동해 엔터를 누릅니다.

완료되면 폴더가 생성된 것을 확인할 수 있습니다.

💡 git clone 으로 프로젝트 생성하기
아래의 명령어를 차례대로 터미널에 입력해주세요.

1. 프로젝트를 생성할 폴더로 이동 이동합니다.
2. git clone https://github.com/nestjs/typescript-starter.git 12-04-nestjs 명령어로 12-04-nestjs 폴더를 생성하고 폴더 안으로 이동합니다.
3. yarn install 을 통해 패키지를 설치합니다.
4. yarn start 로 실행합니다.


NestJS 폴더 구조


NestJS package.json

package.json 파일은 프로젝트 전체 설명서라고 할 수 있습니다.

scripts는 명령어를 쉽게 실행할 수 있도록 단축 명령어를 만들어 둔 것이라고 할 수 있습니다.

 "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },

dependenciesdevDependencies 는 node 라이브러리들의 설치 목록을 나타냅니다.

"dependencies": {
    "@nestjs/common": "^9.0.0",
    "@nestjs/core": "^9.0.0",
    "@nestjs/platform-express": "^9.0.0",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.2.0"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.0.0",
    "@nestjs/schematics": "^9.0.0",
    "@nestjs/testing": "^9.0.0",
    "@types/express": "^4.17.13",
    "@types/jest": "29.5.0",
    "@types/node": "18.15.11",
    "@types/supertest": "^2.0.11",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "^8.0.1",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "jest": "29.5.0",
    "prettier": "^2.3.2",
    "source-map-support": "^0.5.20",
    "supertest": "^6.1.3",
    "ts-jest": "29.0.5",
    "ts-loader": "^9.2.3",
    "ts-node": "^10.0.0",
    "tsconfig-paths": "4.2.0",
    "typescript": "^4.7.4"
  },

실제로 설치는 node_modules 폴더에 되어있습니다.

그래서 node_modules 폴더를 지워도, package.json 에 명시되어 있기 때문에 다시 설치를 할 수 있습니다.

마지막으로 jest 가 있습니다.

"jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
}

jest 는 테스트 관련 부분입니다.

마우스로 하나하나 클릭해서 테스트를 해볼 수 있지만, 백엔드는 보통 보이지 않기 때문에, 명령어를 이용해서 파일을 실행해서 테스트 합니다.

TypeScript

타입스크립트는 자바스크립트에 타입을 부여한 언어입니다.

자바스크립트의 확장된 언어라고 볼 수 있습니다.

타입스크립트는 자바스크립트와 달리 브라우저에서 실행하려면 파일을 한번 변환해주어야 합니다.

이 변환 과정을 우리는 컴파일(compile)이라고 부릅니다.

Why TypeScript?

타입스크립트는 아래 2가지 관점에서 자바스크립트 코드의 품질과 개발 생산성을 높일 수 있습니다.

  • 에러의 사전 방지
  • 코드 가이드 및 자동 완정(개발 생산성 향상)

에러의 사전 방지

타입스크립트의 에러를 사전에 미리 예방할 수 있습니다.

아래 2개의 코드를 비교하여 어떻게 에러를 사전에 방지할 수 있는지 살펴보겠습니다.

function sumJs(a, b) {
  return a + b;
}
function sumTs(a: number, b: number) {
  return a + b;
}

두 코드 모두 두 숫자의 합을 구하는 함수 코드입니다.

sumJs 는 자바스크립트로, sumTs 는 타입스크립트로 작성했습니다.

sum(10, 20); // 30

이렇게 sum 함수를 이용하여 숫자 10과 20을 더합니다.

그러면 저희가 원하는 결과인 30을 얻을 수 있습니다.

그런데 만약 아래와 같이 함수를 호출하면 어떻게 될까요?

sum('10', '20'); // 1020

자바스크립트에 익숙한 분들이라면 위 코드의 결과가 그렇게 헷갈리진 않을 것 입니다.

숫자 대신 문자열을 더하기 때문에 10 + 20 = 30 이 아닌 1020 이라는 결과가 나타납니다.

타입스크립트는 이처럼 의도하지 않은 코드의 동작을 예방할 수 있습니다.

function sumTs(a: number, b: number) {
  return a + b;
}

sumTs('10', '20');	// Error: '10'은 number에 할당할 수 없습니다.

다음은 타입 명시로 의도치 않은 코드의 동직을 예방했습니다.



코드 자동 완성과 가이트

타입스크립트의 또 다른 장점은 코드를 작성할 때 개발 툴의 기능을 최대호 활용할 수 있다는 것 입니다.

요즘에 개발을 할 때 가장 많이 사용되는 Visual Studio Code 는 툴의 내부가 타입스크립트로 작성되어 있어 타입스크립트 개발에 최적화 되어 있습니다.

개발자 관점에서 자바스크립트에 타입에 더해졌을 때 어떠한 장점이 있는지 살펴보기 위해 아래 자바스크립트 코드를 살펴보겠습니다.

function sumJs(a, b) {
  return a + b;
}

var total = sum(10, 20);
total.toLocaleString();

위 코드는 앞에서 살펴봤던 sumJs 함수를 이용하여 두 숫자의 합을 구한 다음 toLocaleString() (특정 언어의 표현 방식에 맞게 숫자를 표기하는 API) 을 적용한 코드입니다.

여기서 toLocaleString() 이라는 API가 어떤 역할을 하는지가 중요한게 아니라 위와 같이 이 코드를 작성할 때 total 이라는 변수의 타입이 코드를 작성하는 시점에 number 라는 것을 자바스크립트가 인지하지 못하고 있는제 중요합니다.

즉, 개발자가 스스로 sumJs 함수의 결과를 예상하고 타입이 number 라고 가정한 상태에서 number 의 API인 toLocaleString() 을 코딩하게 되는 것이죠.

과정을 보면 아래와 같습니다.

위에서 볼 수 있듯이 total 이라는 값이 정해져 있기 않기 때문에 자바스크립트 Number에서 제공하는 API인 toLocaleString() 이라고 했다면 이 sumJs 함수를 실행했을 때만 오류를 확인할 수 있었을 것 입니다.

그렇다면 만약 타입스크립트로 작성하면 어떻게 될까요?

변수 total 에 대한 데이터 타입이 지정되어 있기 때문에 VSCode에서 해당 타입에 대한 API를 미리 보기로 띄워줄 수 있습니다.

TypeScript Basic

TypeScript Start

새로운 폴더 12-05-typescript 를 생성해주세요.

yarn init 을 통해 package.json 을 생성해줍니다.

그리고 yarn add typescript 를 통해 타입스크립트를 설치해줍니다.

{
  "name": "12-05-typescript",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "type": "module",
  "dependencies": {
    "typescript": "^5.0.4"
  }
}

12-05-typescript 폴더의 자바스크립트 파일을 타입스크립트 파일로 컴파일 변환할 수 있습니다.

해당 폴더에 index.ts 파일을 만들어주세요.

터미널에 yarn stc ./index.ts 를 입력해주세요.

해당 디렉토리에 index.js 파일이 새로 생성된 것을 볼 수 있습니다.

이제 컴파일을 할 때 부가적인 옵션들을 부여할 수 있는데 그 옵션들에 대해서 정의할 수 있는 파일이 tsconfig.json 입니다.

기본적으로 key-value 의 형태로 정의되어 있습니다.

yarn tsc --init 을 입력하시면 아래와 같은 메세지와 함께 해당 디렉토리에 tsconfig.json 파일이 생성됩니다.

tsconfig.json 의 컴파일 옵션은 아래 형식을 복사해서 붙여넣기 해주세요.

특별하게 추가할 옵션이 없습니다.

{
  "compilerOptions": {
    "target": "ES2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
    "strict": true,
    "skipLibCheck": true,
  },
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Recommended"
}

String Type

// 문자 타입
let aaa = "안녕하세요";
aaa = 3;

let bbb: string;
bbb = "반갑습니다";
bbb = 123;

다음과 같이 index.tsaaa 에 "안녕하세요" 라는 문자열을 선언 후 다시 숫자 3 을 재선언하게 되면 VSCode에서 'number' 형식은 'string' 형식에 할당할 수 없습니다. 라는 오류를 확인할 수 있습니다.

bbbstring 타입을 지정해주면 bbb 에는 string 타입만 허용됩니다.

Number Type

// 숫자 타입
let ccc: number = 5;
ccc = ""

다음과 같이 index.tscccnumber 타입을 지정해주었습니다.

ccc 에는 number 타입만 허용합니다.

Array Type

타입이 배열 인 경우 간단하게 아래와 같이 선언합니다.

let arr: number[] = [1, 2, 3];

또는 아래와 같이 제네릭 을 사용할 수 있습니다.

let arr: Array<number> = [1, 2, 3];

연산자 를 사용해서 타입을 아래와 같이 정의가 가능합니다.

// 배열 타입
let ddd: number[] = [1, 2, 3, 4, 5, 6];
let eee: (number | string)[] = ["1", 2, 3, 4, 5, 6];
let fff: (number[] | string[]) = [1, 2, 3, 4, 5, 6]

유니온 타입(Union Type)이란 자바스크립트의 OR 연산자( || )와 같이 'A이거나 B이다' 라는 의미의 타입입니다.

타입스크립트에서는 | 를 사용해 OR 연산을 합니다.

ddd 같은 경우에는 number 타입으로만 이루어진 배열만을 정의하였습니다.

eeestring 이거나 number 타입의 배열을 허용합니다.

fffstring 으로 이루어진 배열이거나 number 로 이루어진 배열만 허용합니다.

Object Type

Object(객체) 의 타입을 지정할 때는 interface 를 사용하게 됩니다.

interface 는 상호 간에 정의한 약속 혹은 규칙을 의미합니다.

타입스크립트에서의 interface 는 보통 다음과 같은 범주에 대해 약속을 정의할 수 있습니다.

  • 객체의 스펙(속성과 속성의 타입)
  • 함수의 파라미터
  • 함수의 스펙(파라미터, 반환 타입 등)
  • 배열과 객체를 접근하는 방식
  • 클래스

interface 를 사용해서 객체에 타입을 정의해보겠습니다.

// 객체 타입
interface IProfile {
  school: string
  age: number
}

let profile: IProfile = {
  school: '다람쥐 초등학교',
  age: 13
}

profile.age = "bbb"

다음과 같이 IProfile 이라는 interface 를 생성하고 profile 이라는 객체에 형식을 정의했습니다.

profile.age"bbb" 를 재선언 해줬다면, VSCode에서 'string' 형식은 'number' 형식에 할당할 수 없습니다. 라는 오류를 확인할 수 있습니다.


TypeScript Decorator

데코레이터는 실행하려는 사용자가 구조를 수정하지 않고도 기존 객체에 새로운 기능을 추가할 수 있도록 하는 디자인 패턴입니다.

일반적으로 데코레이트 하려는 함수의 정의 전에 호출됩니다.

데코레이터는 함수를 인수로 얻고 대가로 새로운 함수로 돌려주는 callable(전달받은 object 인자가 호출 가능한지를 판단) 구조입니다.

자바스크립트를 확안한 언어라고 할 수 있는 타입스크립트에서는 실험적인 기능으로 데코레이터를 제공하고 있습니다.

따라서 커맨드 라인이나 tsconfig.json 에서 experimentalDecorators 옵션을 추가해 줘야 합니다.

데코레이터는 앞에서 말한 것처럼 "클래스", "메서드", "접근자", "프로퍼티", "파라미터"에 적용할 수 있습니다.

데코레이터 실습을 진행해보겠습니다.

12-06-typescript-decorator 폴더를 만들어주세요.

yarn init 을 통해 package.json 파일을 생성해주세요.

{
  "name": "12-06-typescript-decorator",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT"
}

yarn add typescript --dev 를 입력해 타입스크립트 모듈을 설치해주세요.

yarn add ts-node 를 입력해 ts-node 를 설치해주세요.

그리고 실행을 위해 스크립트도 작성해줍니다.

타입스크립트 파일을 실행해 볼 수 있습니다.

{
  "name": "12-06-typescript-decorator",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "dev": "ts-node decorator.ts"
  },
  "dependencies": {
    "ts-node": "^10.9.1"
  },
  "devDependencies": {
    "typescript": "^5.0.4"
  }
}

yarn tsc --init 를 입력해 tsconfig.json 을 생성해주세요.

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}

tsconfig.json 은 위와 같고 "experimentalDecorators": true 로 설정해주세요.

12-06-typescript-decoratordecorator.ts 파일을 생성해주세요.

function qqq(aaaaaa) {
    console.log('###########')
    console.log(aaaaaa)
    console.log('###########');
}

@qqq
class MyClass {
    
}

터미널에 yarn dev 를 입력해서 decorator.ts 파일을 실행시켜주세요.

업로드중..

MyClass 클래스 위에 데코레이터 qqq 를 작성했습니다.

MyClass 클래스가 함수의 매개변수로 받아져서 함수 qqq 내부에서 사용되었습니다.

0개의 댓글