728x90
반응형

자바스크립트에서 함수를 선언하는 방식은 크게 두 가지가 있습니다: 함수 선언문(function name() {})과 화살표 함수(const name = () => {}). 이 두 방식은 문법과 개념적 차이로 인해 사용하는 상황이 다릅니다. 이 글에서는 각각의 선언 방식, 차이점, 주로 사용하기 좋은 상황을 살펴보겠습니다.

1. 함수 선언문 (function name())

function 키워드를 사용한 함수 선언 방식은 전통적인 자바스크립트 함수 정의 방식입니다. 이 방식은 함수의 이름을 선언하고, 해당 함수를 호출할 수 있는 이름을 제공합니다.

특징

  • 호이스팅(Hoisting): 함수 선언은 자바스크립트 엔진에 의해 코드의 맨 위로 끌어올려집니다. 따라서 함수 선언문은 함수 선언 위치에 상관없이 호출할 수 있습니다.
  • this 바인딩: function 함수는 호출하는 방식에 따라 this가 동적으로 바뀝니다. 즉, 함수 내부에서 this가 호출된 컨텍스트를 가리킵니다.
  • 생성자 함수로 사용 가능: function 함수는 new 키워드를 통해 객체를 생성할 수 있는 생성자 함수로 사용할 수 있습니다.

예시

function calculateArea(radius) {
  return Math.PI * radius * radius;
}

// 생성자 함수로 사용하는 경우
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const person = new Person('Alice', 25);

주로 사용하기 좋은 상황

  • 일반적인 함수 선언: 특정 기능을 수행하는 일반 함수로 정의할 때 유용합니다.
  • 객체 메서드 정의: 객체 메서드에서 this가 동적으로 바인딩될 필요가 있을 때 사용됩니다.
  • 생성자 함수: new 키워드로 객체를 생성해야 하는 경우에 적합합니다.

2. 화살표 함수 (const name = () => {})

화살표 함수는 ES6에서 도입된 새로운 함수 선언 방식으로, 주로 간결한 표현과 상위 스코프의 this를 유지하는 특징이 있습니다.

특징

  • 호이스팅되지 않음: 화살표 함수는 변수 선언 규칙을 따르기 때문에, 함수가 정의되기 전에 호출할 수 없습니다.
  • 렉시컬 this 바인딩: 화살표 함수는 정의된 위치의 상위 컨텍스트 this를 그대로 사용합니다. 즉, this가 호출 방식에 따라 변경되지 않고 고정됩니다.
  • 생성자로 사용할 수 없음: 화살표 함수는 new 키워드를 사용할 수 없기 때문에 생성자 함수로 사용할 수 없습니다.

예시

// 콜백 함수로 사용하는 경우
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);

// 객체 메서드 내부의 콜백으로 사용하는 경우
const user = {
  name: 'Alice',
  tasks: ['task1', 'task2'],
  showTasks() {
    this.tasks.forEach(task => {
      console.log(`${this.name} has ${task}`);
    });
  }
};
user.showTasks();

주로 사용하기 좋은 상황

  • 콜백 함수: 상위 스코프의 this가 필요한 콜백 함수나 이벤트 핸들러로 자주 사용됩니다.
  • 배열 메서드와 함께 사용: map, filter, forEach 등 간단한 로직을 담은 콜백 함수로 사용하기 적합합니다.
  • 간결한 표현: 로직이 간단한 경우 화살표 함수로 한 줄 표현을 사용해 코드 가독성을 높일 수 있습니다.

차이점 요약

특성 function name() const name = () => {}
호이스팅 호이스팅됨 호이스팅되지 않음
this 바인딩 동적 바인딩 상위 컨텍스트의 this 고정
생성자로 사용 가능 여부 가능 불가능
일반적인 용도 전통적인 함수 정의, 메서드 콜백, 이벤트 핸들러, 간결한 함수 표현

결론

함수 선언 방식과 화살표 함수는 각각 장단점과 적합한 상황이 다릅니다. function 키워드를 통한 함수 선언은 전통적인 함수로 정의할 때, 화살표 함수는 상위 this를 그대로 유지하고 콜백 함수로 사용할 때 유용합니다. 상황에 맞는 함수를 선택하여 코드 가독성과 유지보수성을 높여보세요!

반응형
728x90
반응형

모듈 시스템은 코드를 구조화하고 재사용할 수 있게 해주는 방식으로, 큰 애플리케이션을 작은 단위로 분리하여 관리할 수 있도록 도와줍니다. 모듈 시스템은 코드의 의존성을 명확하게 정의하며, 모듈 간의 상호작용을 효율적으로 처리할 수 있습니다.

※ Java 라이브러리와 Javscript 모듈의 차이

  • Java의 라이브러리컴파일 시점에 주로 의존성을 관리하고, JAR 파일로 배포되는 패키지입니다.(정적)
  • JavaScript의 모듈 시스템은 파일 단위의 모듈을 관리하며, 주로 런타임에 모듈을 로딩하는 방식으로 동작합니다.(동적)
  • Java 9의 모듈 시스템은 컴파일 시점뿐 아니라 런타임 시점에서도 모듈 간의 의존성을 관리하고 검증하는 기능을 강화했습니다. 하지만 이는 JavaScript의 동적 모듈 로딩과 같은 방식은 아닙니다

1. 모듈 시스템의 개념

모듈 시스템은 애플리케이션 내에서 코드를 여러 모듈로 나누고, 각 모듈이 필요한 기능을 내보내고(import/export), 다른 모듈에서 이를 불러와 사용하는 방식을 의미합니다. 모듈 시스템이 없다면, 모든 코드가 하나의 파일이나 전역 스코프에서 작성되어 코드 충돌유지보수의 어려움이 발생할 수 있습니다.

모듈 시스템은 다음과 같은 기능을 제공합니다:

  • 코드 분리 및 재사용성: 모듈을 독립된 단위로 나누어, 코드를 재사용하고 유지보수를 쉽게 할 수 있습니다.
  • 의존성 관리: 모듈 간 의존성을 명확하게 관리하여, 프로젝트의 복잡성을 줄입니다.
  • 캡슐화: 모듈 내부의 코드를 외부에서 접근하지 못하도록 하여, 불필요한 데이터 노출을 막습니다.

JavaScript에서는 주로 CommonJSECMAScript Modules (ESM) 두 가지 모듈 시스템을 사용합니다. 이 두 시스템은 코드의 가져오기(import) 및 내보내기(export) 방식을 정의하며, 각각의 특징에 따라 컴파일 및 런타임에서 다르게 동작합니다.


2. CommonJS (CJS)

CommonJSNode.js에서 사용되는 기본 모듈 시스템으로, 서버 사이드 자바스크립트 환경에서 널리 사용됩니다. CommonJS는 모듈을 동기적으로 로딩하고, 런타임 시 require() 함수를 사용하여 모듈을 불러옵니다.

주요 특징:

  • 모듈 가져오기: require() 함수를 통해 다른 모듈을 가져옵니다.
const moduleA = require('./moduleA');
  • 모듈 내보내기: module.exports로 모듈을 내보냅니다.
module.exports = {
    myFunction: () => { /* ... */ }
};
  • 동기적 로딩: CommonJS는 모듈을 동기적으로 로딩하며, 서버 환경에서 이러한 동기적 특성은 성능 문제를 일으키지 않지만, 브라우저 환경에서는 비효율적일 수 있습니다.

장점:

  • Node.js 기본 모듈 시스템: Node.js 환경에서의 기본 설정으로, 대부분의 Node.js 프로젝트와 라이브러리에서 널리 사용됩니다.
  • 간단한 구조: require()module.exports의 단순한 문법으로 모듈을 쉽게 관리할 수 있습니다.

단점:

  • 브라우저에서의 사용 제한: CommonJS는 브라우저에서 바로 사용할 수 없습니다. 브라우저는 동기적 로딩이 불가능하기 때문에, 번들러(Webpack, Browserify)를 사용해야 합니다.
  • 트리 셰이킹 지원 부족: CommonJS는 트리 셰이킹을 지원하지 않으므로, 불필요한 코드까지 번들링될 수 있습니다.

3. ECMAScript Modules (ESM)

ECMAScript Modules (ESM)는 JavaScript의 표준 모듈 시스템으로, 브라우저Node.js에서 모두 사용됩니다. ESM은 비동기적으로 모듈을 로딩하며, 최신 브라우저 환경과 Node.js 최신 버전에서 기본적으로 지원됩니다.

주요 특징:

  • 모듈 가져오기: import 문을 사용해 모듈을 가져옵니다.
import { myFunction } from './moduleA';
  • 모듈 내보내기: export 키워드로 모듈을 내보냅니다.
export const myFunction = () => { /* ... */ };
  • 비동기적 로딩: ESM은 모듈을 비동기적으로 로딩하여 성능을 최적화합니다. 이는 특히 대규모 웹 애플리케이션에서 유리합니다.

장점:

  • 브라우저와 Node.js 모두 지원: ESM은 브라우저Node.js 환경에서 모두 사용할 수 있습니다.
  • 트리 셰이킹 지원: 사용하지 않는 코드를 제거하여 번들 크기를 줄이고 성능을 향상시킬 수 있습니다.

단점:

  • Node.js 설정 필요: Node.js에서 ESM을 사용하려면 package.json 파일에 "type": "module"을 명시해야 합니다.
  • 기존 CommonJS 코드와의 호환성 문제: CommonJS와 ESM은 서로 호환되지 않기 때문에, 두 시스템을 함께 사용할 때 호환성 문제가 발생할 수 있습니다.

4. 컴파일러와 런타임에서의 동작

CommonJS (CJS):

  • 컴파일: CommonJS는 컴파일 단계 없이 런타임에 require()로 모듈을 가져옵니다.
  • 런타임: 런타임에 모듈을 동기적으로 로딩하며, 한 번 로드된 모듈은 캐시되어 이후 요청 시 캐시된 버전을 사용합니다.

ECMAScript Modules (ESM):

  • 컴파일: ESM은 정적 분석을 통해 모듈 간의 의존성을 컴파일 시점에 분석하며, 트리 셰이킹을 통해 불필요한 코드를 제거할 수 있습니다.
  • 런타임: ESM은 비동기적으로 모듈을 로딩하여, 필요한 시점에만 모듈을 가져옵니다.

5. CommonJS와 ESM의 비교

CommonJS (CJS) ECMAScript Modules (ESM)
모듈 로딩 방식 동기적 로딩 (require) 비동기적 로딩 (import/export)
사용 환경 주로 Node.js 브라우저Node.js 모두 지원
트리 셰이킹 지원하지 않음 트리 셰이킹 지원
컴파일 컴파일 단계 없음, 런타임에서 동작 컴파일 시 의존성 분석 및 최적화 가능
모듈 캐싱 모듈은 한 번 로드된 후 캐시 모듈은 필요할 때 로드됨

결론

JavaScript의 모듈 시스템은 코드의 구조화를 도와 유지보수성을 높이고, 의존성 관리를 명확하게 할 수 있게 해줍니다. CommonJSNode.js 환경에서 기본적으로 사용되며, 서버 사이드 애플리케이션에서 널리 쓰입니다. 반면, ECMAScript Modules (ESM)브라우저Node.js 모두에서 사용할 수 있는 최신 표준으로, 비동기적 로딩과 트리 셰이킹 기능을 지원해 성능과 최적화를 제공합니다.

어떤 모듈 시스템을 사용할지는 프로젝트의 환경필요한 기능에 따라 결정되어야 하며, 최신 웹 애플리케이션이나 브라우저 환경에서는 ESM이 권장됩니다.

반응형
728x90
반응형

Node.js는 자바스크립트를 서버 사이드 프로그래밍 언어로 사용할 수 있게 해주는 런타임 환경입니다. Node.js의 비동기 I/O 모델은 높은 성능과 확장성을 제공하지만, 동기와 비동기 코드의 차이점을 이해하는 것이 중요합니다. 이 글에서는 Node.js에서 비동기 코드를 사용해야 하는 이유를 설명합니다.

이벤트 루프와 싱글 스레드 모델

Node.js는 싱글 스레드로 작동하며, 이벤트 루프를 사용하여 비동기 작업을 처리합니다. 이는 동시에 많은 요청을 효율적으로 처리할 수 있게 합니다.

예시:

const fs = require('fs');

// 비동기 파일 읽기
fs.readFile('/path/to/file', (err, data) => {
  if (err) throw err;
  console.log('파일 내용:', data.toString());
});

console.log('이 코드가 먼저 실행됩니다.');

동기 함수의 문제점

동기 함수는 이벤트 루프를 차단하여 다른 작업을 처리할 수 없게 만듭니다. 이는 서버의 성능과 확장성에 부정적인 영향을 미칩니다.

예시:

const fs = require('fs');

// 동기 파일 읽기
const data = fs.readFileSync('/path/to/file');
console.log('파일 내용:', data.toString());

console.log('이 코드가 나중에 실행됩니다.');

동기 함수의 문제점:

  1. 병목 현상:

    • 다른 클라이언트 요청이 큐에서 대기하게 됩니다.
    • 예를 들어, 해싱 작업이 500ms 걸린다면, 그 동안 서버는 다른 요청을 처리하지 못합니다.
  2. 응답 지연:

    • 각 요청이 완료될 때까지 대기 시간이 길어집니다.
    • 여러 요청이 동시 도착하면 서버 응답이 매우 느려질 수 있습니다.
  3. 비효율적 자원 사용:

    • 서버 자원이 유휴 상태로 대기하는 시간이 늘어납니다.
    • CPU와 메모리를 효율적으로 사용하지 못합니다.

비동기 함수의 이점

비동기 함수는 이벤트 루프를 차단하지 않으므로, Node.js가 더 많은 동시 작업을 처리할 수 있게 합니다. 이는 특히 I/O 바운드 작업에서 중요한 장점입니다.

예시:

const express = require('express');
const app = express();
const fs = require('fs');

app.get('/file', (req, res) => {
  fs.readFile('/path/to/file', (err, data) => {
    if (err) {
      res.status(500).send('파일 읽기 오류');
      return;
    }
    res.send(`파일 내용: ${data.toString()}`);
  });
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

비동기 함수의 이점:

  1. 동시성 향상:

    • 해싱 작업이 진행되는 동안에도 다른 클라이언트 요청을 처리할 수 있습니다.
    • 이는 병목 현상을 줄이고, 서버의 처리량을 높입니다.
  2. 빠른 응답 시간:

    • 요청이 병렬로 처리되므로 응답 시간이 단축됩니다.
    • 사용자 경험이 개선됩니다.
  3. 효율적 자원 사용:

    • CPU와 메모리를 효율적으로 사용하여 서버 성능이 최적화됩니다.
    • 더 많은 요청을 처리할 수 있어 서버의 확장성이 향상됩니다.

결론

Node.js의 비동기 I/O 모델은 싱글 스레드 환경에서 높은 성능과 확장성을 제공할 수 있게 합니다. 비동기 코드를 사용하면 이벤트 루프가 차단되지 않아 더 많은 동시 요청을 처리할 수 있습니다. 따라서, Node.js에서 비동기 코드를 사용하는 것은 서버 성능과 효율성을 높이는 데 필수적입니다.

반응형
728x90
반응형

TypeScript 애플리케이션을 개발하고 배포할 때는 두 가지 주요 방식이 있습니다: 컴파일된 JavaScript 코드를 실행하는 것과 TypeScript 코드를 직접 실행하는 것입니다. 이 글에서는 각 방법의 차이점과 설정 방법에 대해 설명하고, 이를 위해 필요한 도구와 예시 코드를 제공합니다.

컴파일된 코드 vs TypeScript 직접 실행

1. 컴파일된 코드 실행

  • TypeScript 코드를 JavaScript로 컴파일한 후, 컴파일된 JavaScript 파일을 실행합니다.
  • 일반적으로 프로덕션 환경에서 사용됩니다.
  • 장점: 실행 속도가 빠르고, JavaScript 환경과 호환성이 높습니다.
  • 도구: PM2 (실제로 Node.js를 사용하여 애플리케이션을 실행합니다)

2. TypeScript 직접 실행

  • TypeScript 코드를 직접 실행하여, 개발 과정에서 즉시 결과를 확인할 수 있습니다.
  • 주로 개발 환경에서 사용됩니다.
  • 장점: 코드 변경 시 바로 반영되어 빠른 피드백을 받을 수 있습니다.
  • 도구: nodemon, ts-node

PM2 설정 (프로덕션 환경)

PM2는 프로덕션 환경에서 애플리케이션을 관리하고 모니터링하는 데 사용됩니다. TypeScript 코드를 JavaScript로 컴파일한 후, 컴파일된 파일을 실행합니다.

ecosystem.config.json 예시

{
  "apps": [
    {
      "name": "example",
      "script": "node -r dotenv/config ./dist/src/index.js",
      "watch": true,
      "ignore_watch": ["config", "logs", "node_modules", "public", "src"],
      "env": {
        "PORT": 4000,
        "NODE_ENV": "production"
      }
    }
  ]
}
  • script: 컴파일된 JavaScript 파일 경로.
  • watch: 파일 변경 감시.
  • ignore_watch: 감시에서 제외할 디렉토리.
  • env: 환경 변수 설정.

Nodemon 설정 (개발 환경)

Nodemon은 파일 변경을 감시하고, TypeScript 파일을 직접 실행하여 개발 중 변경 사항을 즉시 반영합니다. ts-nodetsconfig-paths를 사용하여 TypeScript 경로 매핑 문제를 해결할 수 있습니다.

nodemon.json 예시

{
  "watch": ["src"],
  "ext": "ts,json",
  "exec": "ts-node -r tsconfig-paths/register -r dotenv/config ./src/index.ts"
}
  • watch: 감시할 디렉토리 목록.
  • ext: 감시할 파일 확장자.
  • exec: 파일 변경 시 실행할 명령어.

NestJS와 경로 설정

NestJS 프로젝트는 nest start 명령어를 사용하여 서버를 시작합니다. 이는 NestJS의 CLI(Command Line Interface)를 통해 제공되는 명령어로, 프로젝트의 main.ts 파일을 실행하여 서버를 시작합니다. NestJS는 내부적으로 필요한 설정을 자동으로 처리하여 개발자가 직접 경로를 설정하지 않아도 됩니다.

NestJS 프로젝트의 동작 방식

  1. Nest CLI:
    • nest start 명령어는 Nest CLI를 사용하여 프로젝트를 시작합니다.
    • CLI는 main.ts 파일을 찾아 실행합니다.
    • TypeScript 파일을 직접 실행하기 위해 ts-node를 사용합니다.

예시: NestJS 프로젝트의 main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

PM2와 NestJS CLI 사용하는 방식

NestJS 프로젝트에서 PM2를사용할 수 있습니다. NestJS의 실행 파일을 사용하여 애플리케이션을 시작할 수 있습니다.

PM2 설정

PM2를 사용하여 NestJS 애플리케이션을 프로덕션 환경에서 실행하려면, dist 디렉토리에 컴파일된 JavaScript 파일을 실행합니다.

ecosystem.config.js 예시

module.exports = {
  apps: [
    {
      name: 'nestjs-app',
      script: 'dist/main.js',
      watch: false,
      env: {
        NODE_ENV: 'production',
      },
    },
  ],
};

NestJS CLI를 사용하여 자동 재시작 설정

NestJS CLI의 --watch 옵션을 사용하면, 파일 변경을 감지하고 자동으로 서버를 재시작할 수 있습니다. 이를 위해 다음 명령어를 터미널에서 실행합니다:

nest start --watch

이 명령어는 파일 변경을 감지하고, 변경이 있을 때마다 서버를 자동으로 재시작합니다.

결론

TypeScript 애플리케이션을 개발하고 배포할 때, 컴파일된 코드를 실행할지 또는 TypeScript 코드를 직접 실행할지에 따라 설정 방법이 다릅니다. 프로덕션 환경에서는 PM2를 사용하여 컴파일된 코드를 실행하고, 개발 환경에서는 nodemon을 사용하여 TypeScript 파일을 직접 실행하는 것이 일반적입니다. 이를 통해 각 환경에 최적화된 개발 및 배포 프로세스를 구축할 수 있습니다.

NestJS CLI를 사용하면 경로 설정, 환경 변수 로드, 빌드 및 실행 등 다양한 작업을 자동으로 처리해 주기 때문에 개발자는 더욱 편리하게 개발할 수 있습니다. 이를 통해 TypeScript 애플리케이션의 개발 및 배포 과정을 더욱 효율적으로 관리할 수 있을 것입니다.

반응형
728x90
반응형

TypeScript에서는 typeinterface를 사용하여 타입을 정의할 수 있습니다. 이 두 가지는 많은 경우에 상호 교환이 가능하지만, 각각의 고유한 장점과 사용 사례가 있습니다. 이 문서에서는 typeinterface의 차이점과 언제 사용하는 것이 좋은지에 대해 설명합니다.

type 정의

type을 사용하면 간단한 데이터 구조, 유니언 타입, 교차 타입, 기존 타입의 변형 등을 정의할 수 있습니다. type은 매우 유연하며 다양한 상황에서 사용될 수 있습니다.

간단한 데이터 구조 정의

export type User = {
  id: number;
  name: string;
  email: string;
};

유니언 타입 정의

export type Response = SuccessResponse | ErrorResponse;

export type SuccessResponse = {
  status: "success";
  data: any;
};

export type ErrorResponse = {
  status: "error";
  message: string;
};

교차 타입 정의

export type DetailedUser = User & {
  address: string;
  phone: string;
};

기존 타입의 변형

export type PartialUser = Partial<User>; // 모든 필드를 선택적으로 변경
export type PickUser = Pick<User, "id" | "name">; // 특정 필드만 선택
export type OmitUser = Omit<User, "email">; // 특정 필드를 제외

interface 정의

interface는 주로 객체의 구조를 정의하는 데 사용되며, 클래스와의 상호작용이 많을 때 특히 유용합니다. interface는 확장성 측면에서 매우 강력합니다.

인터페이스 정의

interface User {
  id: number;
  name: string;
  email: string;
}

인터페이스 확장

interface Person {
  name: string;
}

interface Employee extends Person {
  employeeId: number;
}

타입 병합

interface는 동일한 이름으로 여러 번 정의될 수 있으며, 자동으로 병합됩니다.

interface Person {
  age: number;
}

typeinterface의 차이

특징 type interface
확장성 유니언, 교차 타입 등 복잡한 타입 변형에 적합 다른 인터페이스를 확장하거나 병합할 수 있음
타입 정의 간단한 데이터 구조, 유니언, 교차 타입 정의에 유용 객체의 구조를 정의하는 데 유용
재정의 새로운 속성을 추가할 수 없음 동일한 이름으로 여러 번 정의하여 병합 가능
사용 사례 함수 타입, 튜플 타입, 복잡한 객체 타입 등 클래스와의 상호작용, 명확한 객체 구조 정의

언제 type을 사용해야 할까?

  • 간단한 데이터 구조 정의: 간단한 데이터 구조를 정의할 때 type을 사용하는 것이 좋습니다.
  • 유니언 타입과 교차 타입: 여러 타입을 조합하거나 선택할 수 있도록 하는 데 적합합니다.
  • 유연한 타입 정의: 함수 타입, 튜플 타입, 복잡한 객체 타입 등을 정의할 때 매우 유용합니다.
  • 기존 타입의 변형: 특정 필드를 선택하거나 제외하여 새로운 타입을 정의할 때 유용합니다.

예시

export type User = {
  id: number;
  name: string;
  email: string;
};

export type Response = SuccessResponse | ErrorResponse;

export type SuccessResponse = {
  status: "success";
  data: any;
};

export type ErrorResponse = {
  status: "error";
  message: string;
};

export type DetailedUser = User & {
  address: string;
  phone: string;
};

export type PartialUser = Partial<User>;
export type PickUser = Pick<User, "id" | "name">;
export type OmitUser = Omit<User, "email">;

결론

typeinterface는 각각의 고유한 장점과 사용 사례가 있습니다.

  • DTO와 같은 복잡한 데이터 구조: 클래스와 interface를 사용하여 명확하게 정의하고 유효성 검사를 추가하는 것이 좋습니다.
  • 간단한 데이터 구조: type을 사용하여 정의할 수 있습니다.
  • 유니언 타입, 교차 타입, 복잡한 타입 변형: type을 사용하는 것이 더 적합합니다.

상황에 맞게 적절한 방식을 선택하여, 코드의 가독성과 유지보수성을 높일 수 있습니다.

반응형
728x90
반응형

JavaScript와 TypeScript에서 모듈을 가져오는 방법에는 여러 가지가 있습니다. 특히 import * asimport 구문은 각각의 사용 사례와 장점이 있습니다. 이 문서에서는 이 두 가지 방법의 차이점과 사용 사례에 대해 설명합니다.

import * as

import * as 구문은 모듈의 모든 내보내기를 하나의 네임스페이스 객체로 가져옵니다. 이를 통해 모듈의 다양한 내보내기 항목에 접근할 수 있습니다.

사용 예시

math.ts (TypeScript 모듈)

// math.ts
export function add(x: number, y: number): number {
  return x + y;
}

export function subtract(x: number, y: number): number {
  return x - y;
}

main.ts (네임스페이스 객체 사용)

// main.ts
import * as math from './math';

console.log(math.add(2, 3));       // 5
console.log(math.subtract(5, 2));  // 3

장점

  • 모듈의 모든 내보내기에 접근할 수 있습니다.
  • 네임스페이스 객체를 통해 모듈의 내보내기를 체계적으로 관리할 수 있습니다.

import {}

import {} 구문은 모듈의 특정 내보내기 항목을 선택적으로 가져옵니다. 이를 통해 필요한 항목만 가져와 사용할 수 있습니다.

사용 예시

math.ts (TypeScript 모듈)

// math.ts
export function add(x: number, y: number): number {
  return x + y;
}

export function subtract(x: number, y: number): number {
  return x - y;
}

main.ts (선택적 내보내기 사용)

// main.ts
import { add, subtract } from './math';

console.log(add(2, 3));       // 5
console.log(subtract(5, 2));  // 3

장점

  • 필요한 내보내기 항목만 가져와서 사용할 수 있어 코드의 가독성과 효율성을 높일 수 있습니다.
  • 불필요한 내보내기를 가져오지 않으므로, 모듈 로딩 시간을 단축할 수 있습니다.

import default

import default 구문은 모듈이 단일 기본 내보내기를 제공하는 경우에 사용됩니다.

사용 예시

math.ts (TypeScript 모듈)

// math.ts
export default function add(x: number, y: number): number {
  return x + y;
}

main.ts (기본 내보내기 사용)

// main.ts
import add from './math';

console.log(add(2, 3)); // 5

장점

  • 모듈의 기본 내보내기를 간편하게 가져올 수 있습니다.
  • 기본 내보내기는 모듈에서 단일 객체나 함수 등을 내보낼 때 유용합니다.

결론

  • import * as: 네임스페이스 객체를 통해 모듈의 모든 내보내기에 접근할 때 사용합니다.
  • import {}: 모듈의 특정 내보내기 항목을 선택적으로 가져올 때 사용합니다.
  • import default: 모듈의 단일 기본 내보내기를 가져올 때 사용합니다.

상황에 맞게 적절한 가져오기 구문을 선택하면 코드의 가독성과 유지보수성을 향상시킬 수 있습니다.

반응형
728x90
반응형

Node.js는 JavaScript 코드를 서버 측에서 실행하기 위한 런타임 환경입니다. V8 JavaScript 엔진을 기반으로 하며, 고성능 비동기 이벤트 기반 아키텍처를 제공합니다. 이번 글에서는 Node.js의 구성 요소와 그 역할에 대해 알아보겠습니다.

Node.js란?

Node.js는 서버 사이드에서 JavaScript를 실행할 수 있는 오픈 소스, 크로스 플랫폼 런타임 환경입니다. 주로 서버 측 애플리케이션을 개발하는 데 사용되며, 다음과 같은 특징을 가지고 있습니다:

  • 비동기 이벤트 기반 아키텍처
  • 높은 처리 성능
  • 빠른 개발 속도

Node.js의 주요 구성 요소

Node.js는 여러 구성 요소로 이루어져 있습니다. 이 구성 요소들은 함께 작동하여 JavaScript 코드를 서버 측에서 실행하고 다양한 기능을 제공합니다.

1. V8 엔진

V8 엔진은 Google이 개발한 오픈 소스 JavaScript 엔진입니다. Node.js는 이 엔진을 사용하여 JavaScript 코드를 실행합니다. V8 엔진은 다음과 같은 특징을 가지고 있습니다:

  • 고성능: Just-In-Time (JIT) 컴파일을 통해 JavaScript 코드를 빠르게 실행합니다.
  • 광범위한 지원: 최신 ECMAScript 표준을 지원합니다.

2. libuv

libuv는 Node.js의 비동기 I/O를 처리하는 라이브러리입니다. Node.js의 이벤트 루프와 비동기 작업을 관리하는 핵심 역할을 합니다. libuv는 다음과 같은 기능을 제공합니다:

  • 이벤트 루프: 비동기 작업을 처리하기 위한 이벤트 루프를 관리합니다.
  • 비동기 I/O: 파일 시스템, 네트워크, 타이머 등의 비동기 작업을 처리합니다.

3. Node.js 표준 라이브러리

Node.js는 다양한 내장 모듈을 포함하여 서버 측 애플리케이션 개발에 필요한 기능을 제공합니다. 주요 내장 모듈은 다음과 같습니다:

  • HTTP 모듈: HTTP 서버 및 클라이언트를 생성할 수 있습니다.
  • File System (fs) 모듈: 파일 시스템에 접근하여 파일을 읽고 쓸 수 있습니다.
  • Stream 모듈: 스트림을 사용하여 데이터의 읽기/쓰기 작업을 효율적으로 처리할 수 있습니다.
  • Events 모듈: 이벤트 기반 프로그래밍을 지원합니다.

4. C++ 바인딩

Node.js는 C++로 작성된 네이티브 코드와 JavaScript 코드 간의 상호 작용을 가능하게 하는 바인딩을 제공합니다. 이를 통해 성능이 중요한 작업을 네이티브 코드로 구현할 수 있습니다.

비동기 이벤트 기반 아키텍처

Node.js는 비동기 이벤트 기반 아키텍처를 채택하여 높은 성능을 제공합니다. 이는 다음과 같은 방식으로 작동합니다:

  1. 비동기 작업 요청: 파일 읽기/쓰기, 네트워크 요청 등 비동기 작업이 요청되면 즉시 처리되지 않고 이벤트 루프에 등록됩니다.
  2. 이벤트 루프: 이벤트 루프는 비동기 작업이 완료될 때까지 기다렸다가, 작업이 완료되면 콜백 함수를 실행합니다.
  3. 콜백 함수 실행: 비동기 작업이 완료되면 콜백 함수가 실행되어 결과를 처리합니다.

예제 코드

다음은 Node.js에서 HTTP 서버를 생성하는 간단한 예제 코드입니다:

const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

이 코드는 HTTP 서버를 생성하고, 요청이 들어오면 "Hello, World!" 메시지를 응답으로 반환합니다.

결론

Node.js는 V8 엔진과 libuv 라이브러리를 기반으로 하여 고성능 비동기 이벤트 기반 아키텍처를 제공하는 JavaScript 런타임 환경입니다. 다양한 내장 모듈과 네이티브 코드와의 상호 작용을 통해 강력한 서버 사이드 애플리케이션을 개발할 수 있습니다.

Node.js를 사용하면 JavaScript의 장점을 서버 측에서도 활용할 수 있으며, 빠른 개발과 높은 성능을 동시에 달성할 수 있습니다.

반응형
728x90
반응형

프로그래밍에서 상수 집합을 정의하고 사용할 때, 우리는 객체를 사용할 수도 있고, TypeScript에서 제공하는 enum 타입을 사용할 수도 있습니다. 이 글에서는 enum을 사용하는 이유와 그 장점을 정리해보겠습니다.

객체를 사용한 상수 표현

먼저, 객체를 사용하여 상수를 표현하는 방법을 살펴보겠습니다.

const Direction = {
  UP: "UP",
  DOWN: "DOWN",
  LEFT: "LEFT",
  RIGHT: "RIGHT",
};

객체를 사용하면 상수를 간편하게 정의할 수 있지만, 몇 가지 단점이 존재합니다.

enum을 사용한 상수 표현

이제, enum을 사용하여 상수를 표현하는 방법을 살펴보겠습니다.

enum Direction {
  UP = "UP",
  DOWN = "DOWN",
  LEFT = "LEFT",
  RIGHT = "RIGHT",
}

enum은 여러 상수를 하나의 집합으로 정의할 때 유용합니다.

차이점

1. 타입 안전성

  • 객체: 객체를 사용할 때는 상수 값에 접근할 때 타입 체크가 엄격하지 않습니다. 문자열 상수를 직접 사용할 수 있어, 오타 등의 실수를 컴파일 타임에 잡지 못할 수 있습니다.

  • enum: enum을 사용하면 타입스크립트가 컴파일 타임에 타입을 체크하므로, 오타나 잘못된 값 사용을 방지할 수 있습니다.

// 객체를 사용할 때
const myDirection = Direction.UPWARDS; // 컴파일 타임에 오류를 잡지 못함

// enum을 사용할 때
const myDirection: Direction = Direction.UP; // 컴파일 타임에 오류를 잡음

2. 가독성 및 유지보수성

  • 객체: 객체를 사용하면 코드가 덜 명확할 수 있으며, 상수 집합을 관리하기가 어려울 수 있습니다.

  • enum: enum은 상수 집합을 명확하게 정의하여 가독성과 유지보수성을 높입니다. enum은 값의 집합을 의미 있는 이름으로 그룹화하여 가독성을 높입니다.

3. 자동 완성 및 IDE 지원

  • 객체: 객체를 사용할 때는 자동 완성 기능이 제한적일 수 있습니다. IDE는 객체의 키에 대해 어느 정도 자동 완성을 제공할 수 있지만, enum만큼 강력하지 않습니다.

  • enum: enum을 사용하면 IDE에서 자동 완성 및 타입 정보를 잘 제공하므로, 개발자가 실수할 가능성이 줄어듭니다.

4. 리버스 매핑

  • 객체: 객체를 사용하면 값을 키로 리버스 매핑하는 것이 번거로울 수 있습니다.

  • enum: TypeScript의 숫자 enum은 리버스 매핑을 지원합니다. 문자열 enum은 리버스 매핑을 지원하지 않지만, 키-값 쌍으로 정의되어 있어 사용이 편리합니다.

예시

// 객체를 사용할 때
const Colors = {
  RED: "RED",
  GREEN: "GREEN",
  BLUE: "BLUE",
};

const favoriteColor = Colors.RED;
const invalidColor = "RED"; // 컴파일 타임에 오류 없음, 실수 가능

// enum을 사용할 때
enum Colors {
  RED = "RED",
  GREEN = "GREEN",
  BLUE = "BLUE",
}

const favoriteColor: Colors = Colors.RED;
const invalidColor: Colors = "RED"; // 컴파일 타임에 오류 발생, 실수 방지

결론

  • 객체 사용: 간단한 상수 집합을 정의하는 데는 유용하지만, 타입 안전성과 코드 가독성이 떨어질 수 있습니다.

  • enum 사용: 타입 안전성, 가독성, 유지보수성, 자동 완성 기능에서 많은 장점이 있습니다. 특히 큰 프로젝트나 많은 상수 값을 다루는 경우 enum을 사용하는 것이 좋습니다.

TypeScript에서 enum은 객체를 사용한 상수 표현보다 타입 안전성과 코드 관리 측면에서 더 나은 선택입니다. 이를 통해 코드의 가독성, 유지보수성, 타입 안전성을 높일 수 있습니다.

반응형
728x90
반응형

TypeScript는 정적 타입을 제공하여 컴파일 타임에 코드의 타입을 확인할 수 있도록 도와줍니다. 하지만 런타임에서는 JavaScript로 변환된 코드가 실행되기 때문에 타입 검사가 적용되지 않습니다. 이를 이해하기 위해 컴파일 타임과 런타임의 차이를 살펴보고, URL 파라미터를 처리하는 방법을 알아보겠습니다.

컴파일 타임과 런타임의 차이

컴파일 타임

  • 컴파일 타임은 TypeScript 코드가 JavaScript 코드로 변환되는 단계입니다. 이 과정에서 TypeScript 컴파일러는 타입 검사를 수행하여 타입 오류가 있는지 확인합니다. 타입 오류가 없다면 TypeScript 코드는 JavaScript로 변환됩니다.

  • 예를 들어, 다음과 같은 TypeScript 코드를 작성했다고 가정해보겠습니다:

import { FastifyRequest, FastifyReply } from 'fastify';

const handler = async (req: FastifyRequest<{ Params: { id: number }; Body: any }>, reply: FastifyReply) => {
  const id = req.params.id;
  console.log(typeof id); // 컴파일 타임에 TypeScript는 id를 number로 인식합니다.
};
  • 컴파일 타임에 idnumber 타입으로 인식되기 때문에 타입 오류가 발생하지 않습니다.

런타임

  • 런타임은 애플리케이션이 실제로 실행되는 단계입니다. 이 단계에서는 JavaScript 코드가 실행되며, 모든 URL 파라미터는 문자열로 전달됩니다. 이는 JavaScript의 기본 동작입니다.

  • 다음은 런타임에서 URL 파라미터가 문자열로 전달되는 예시입니다:

const fastify = require('fastify')();

fastify.get('/example/:id', async (request, reply) => {
  const id = request.params.id;
  console.log(typeof id); // 런타임에서는 id가 항상 string 타입입니다.
  const numericId = Number(id);
  console.log(typeof numericId); // number
});

fastify.listen(3000, err => {
  if (err) throw err;
  console.log('Server listening on http://localhost:3000');
});
  • 런타임에 URL 파라미터 id는 문자열로 전달되므로, 이를 숫자로 변환해야 합니다.

정리

  • 컴파일 타임: TypeScript 컴파일러가 코드를 JavaScript로 변환하는 단계로, 이 과정에서 타입 검사가 수행됩니다. 이 단계에서는 URL 파라미터가 전달되지 않기 때문에 id가 문자열로 전달되는 동작은 발생하지 않습니다.

  • 런타임: 애플리케이션이 실제로 실행되는 단계로, 모든 URL 파라미터가 문자열로 전달됩니다. 따라서 런타임에서는 URL 파라미터를 명시적으로 숫자로 변환해야 합니다.

TypeScript의 타입 시스템은 컴파일 타임에만 적용되므로, 런타임에서 타입 안전성을 보장하기 위해 명시적인 타입 변환이 필요합니다. 특히 URL 파라미터와 같은 경우에는 명시적으로 숫자로 변환하는 것이 중요합니다.

이렇게 컴파일 타임과 런타임의 차이를 이해하고, 적절한 타입 변환을 통해 TypeScript와 JavaScript를 활용하여 안전하고 효율적인 코드를 작성할 수 있습니다.

반응형
728x90
반응형

TypeScript는 정적 타입을 제공하여 컴파일 타임에 코드의 타입을 확인할 수 있도록 도와줍니다. 이번 포스트에서는 TypeScript 컴파일 과정과 NestJS 프로젝트에서 TypeScript 파일을 컴파일하고 실행하는 방법을 알아보겠습니다.

TypeScript 컴파일 과정

TypeScript 컴파일 과정은 Java의 컴파일 과정과 유사합니다. TypeScript 코드는 tsc 명령어를 사용하여 JavaScript 코드로 변환됩니다. 다음은 TypeScript 컴파일 과정에 대한 설명입니다.

컴파일 타임

컴파일 타임은 TypeScript 코드가 JavaScript 코드로 변환되는 단계입니다. 이 과정에서 TypeScript 컴파일러는 타입 검사를 수행하여 타입 오류가 있는지 확인합니다. 타입 오류가 없다면 TypeScript 코드는 JavaScript로 변환됩니다.

예를 들어, 다음과 같은 TypeScript 코드를 작성했다고 가정해보겠습니다:

const greet = (name: string): string => {
  return `Hello, ${name}!`;
};

const message = greet("World");
console.log(message);

컴파일 타임에 tsc 명령어를 사용하여 TypeScript 코드를 JavaScript 코드로 변환합니다:

tsc greet.ts

이 명령어를 실행하면 greet.js 파일이 생성됩니다.

런타임

런타임은 애플리케이션이 실제로 실행되는 단계입니다. 이 단계에서는 변환된 JavaScript 코드가 실행됩니다. 다음은 변환된 JavaScript 코드를 실행하는 예시입니다:

var greet = function (name) {
    return "Hello, " + name + "!";
};
var message = greet("World");
console.log(message);

런타임에 Node.js를 사용하여 JavaScript 파일을 실행합니다:

NestJS 프로젝트에서 TypeScript 컴파일

NestJS 프로젝트에서는 nest start 명령어를 사용하여 TypeScript 파일을 컴파일하고 실행합니다. NestJS CLI는 기본적으로 TypeScript 컴파일러(tsc)를 사용합니다.

tsconfig.json 파일 설정

tsconfig.json 파일은 TypeScript 컴파일러 옵션을 정의합니다.

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

package.json 파일 설정

package.json 파일에 정의된 스크립트를 통해 yarn 명령어로 프로젝트를 빌드하고 실행할 수 있습니다.

{
  "scripts": {
    "build": "tsc",
    "start": "nest start",
    "start:dev": "cross-env NODE_ENV=development nest start --watch",
    "start:prod": "cross-env NODE_ENV=production nest start"
  },
  "devDependencies": {
    "@nestjs/cli": "^7.5.1",
    "typescript": "^4.1.3",
    "ts-node": "^9.1.1",
    "cross-env": "^7.0.3"
  }
}

프로젝트 구동

  • 개발 모드:

    yarn start:dev
    • cross-env NODE_ENV=development nest start --watch 명령어가 실행됩니다.
    • 파일 변경을 감지하고 자동으로 재시작하는 개발 모드로 애플리케이션이 실행됩니다.
  • 프로덕션 모드:

    yarn start:prod
    • cross-env NODE_ENV=production nest start 명령어가 실행됩니다.
    • 프로덕션 환경으로 애플리케이션이 실행됩니다.

요약

  • nest start 명령어를 사용하면 NestJS CLI가 tsc를 사용하여 TypeScript 파일을 컴파일하고, 컴파일된 JavaScript 파일을 실행합니다.
  • package.json 파일에 정의된 스크립트를 통해 yarn 명령어로 프로젝트를 빌드하고 실행할 수 있습니다.

이렇게 하면 TypeScript 컴파일과 NestJS 프로젝트의 빌드 및 실행 과정을 이해하고, 효율적으로 프로젝트를 관리할 수 있습니다.

반응형