TypeScript 프로젝트에서 constants 관리하기

haaaalin·2023년 11월 28일
post-thumbnail

항상 Java + Spring boot 조합에서는 토이 프로젝트라, 규모가 작기도 했고, 따로 고민해보지 않았습니다. TypeScript 라는 언어에서는 상수를 어떻게 관리하는 지 문득 궁금해지기도 했고, 어떻게 해야 가장 효율적으로 관리할 수 있을지 고민해봤습니다!🙃

TypeScript의 다양한 상수 선언 방법

class의 static property 이용하기

export class ErrorCode {
	static readonly ERROR_NOT_FOUND = '데이터를 찾을 수 없습니다.';
	static readonly ERROR_AUTH_FAILD = '인증에 실패했습니다.';
}

[문제점] 역할에 맞지 않는 타입

우리가 원하는 방향은 class의 인스턴스를 생성하지 않고, static readonly로 선언된 변수를 사용하는 것입니다. 하지만, static 멤버 변수를 갖고 있는 친구는 상수를 관리하는 역할 이전에 class이기 떄문에 "상수 변수를 관리하는 건지...인스턴스를 생성해 동적으로 사용하는 건지..." 딱 코드를 봤을 때 혼란이 올 수 있어, 가독성과 생산성이 떨어질거라 판단됩니다. 🥲

const enum 이용하기

export const enum ColorCode {  
  RED = '#1010',  
  BLUE = '#1020',  
  GREEN = '#1030',  
}

const 키워드를 이용해 enum을 정의하면, 트랜스파일할 때 inline으로 확장됩니다. 추가적인 코드가 발생하지 않아, 로딩 속도나 성능을 걱정할 필요가 없습니다.

아래 코드를 트랜스파일하면?

const colors = [ColorCode.RED, ColorCode.BLUE, ColorCode.GREEN];

inline으로 확장된 모습을 볼 수 있습니다.

var colors = ["#1010" /* ColorCode.RED */, "#1020" /* ColorCode.BLUE */, "#1030" /* ColorCode.GREEN */];

[번외] enum의 문제점

1️⃣ 트랜스파일 시 생성되는 코드의 양이 많다

const 키워드 유무에 따른 컴파일 코드를 한 번 비교해보겠습니다. 아래는 COLOR_CODE만 정의되어 있는 ts파일을 트랜스파일한 결과입니다. 뭔가 많아졌습니다.. 😂

"use strict";
exports.__esModule = true;
exports.ColorCode = void 0;
var ColorCode;
(function (ColorCode) {
    ColorCode["RED"] = "#1010";
    ColorCode["BLUE"] = "#1020";
    ColorCode["GREEN"] = "#1030";
})(COLOR = exports.ColorCode || (exports.ColorCode = {}));

아래는 const키워드를 이용해 정의한 COLOR_CODE의 컴파일한 결과입니다. 아주 그냥 COLOR_CODE의 흔적은 찾아볼 수 없습니다.

"use strict";
exports.__esModule = true;

2️⃣ Tree-shaking이 되지 않는다

Tree-shaking이란?
나무를 흔들면 잎사귀들이 떨어져 나가는 현상을 의미하는 말입니다. 이를 코드에 적용하면, 바로 사용하지 않는 코드를 삭제하는 거죠.🍃
export했지만, 아무곳에서도 사용하지 않는 코드를 삭제해 번들 크기를 줄여, 페이지가 표시되는 시간을 단축시켜줍니다.

앞서 봤듯이 enum을 트랜스파일하면, JavaScript에 존재하지 않는 것을 구현하기 위해, IIFE를 생성하는데 이를 '사용하지 않는 코드'로 판단할 수 없어, Tree-shaking이 적용되지 않습니다.

Object literal 이용하기

const ColorCode = {
  RED: '#1010',
  PURPLE: '#1020',
  ORANGE: '#1030',
  YELLOW: '#1040',
  GREEN: '#1050',
  BLUE: '#1060'
} as const;

enum과 거의 유사한 모습이지만, Tree-shaking이 가능하고, 트랜스파일 시에 추가로 생성되는 코드의 양이 적습니다. 궁금해서 트랜스파일 해보니 아래와 같았습니다.

var ColorCode = {
    RED: '#1010',
    PURPLE: '#1020',
    ORANGE: '#1030',
    YELLOW: '#1040',
    GREEN: '#1050',
    BLUE: '#1060'
};

🧐 프로젝트 크기에 따른 상수 관리 방법

소규모 프로젝트

만약 규모가 작은 어플리케이션 개발이라면, 단순히 global 폴더에 constants.ts 파일을 생성해 파일 하나에 모두 때려박는 방법을 택할 수 있습니다. constants.ts의 code 라인이 100~200줄 이하라면 괜찮을 것 같습니다.

대규모 프로젝트

대부분의 프로젝트는 확장 가능성을 고려해야 하므로 각 도메인에 따라, 또는 모듈에 따라 여러 개의 constant 파일을 만드는 것이 좋습니다.

✏️ 마무리하며

TypeScript의 상수 선언 방법을 찾아보니 정말 다양했고, 같은 것이라 생각했지만 사소한 차이로 서로 다른 방법들도 다시 한 번 짚고 넘어갈 수 있었습니다. const enum, as const, 그냥 const, enum, namespace 등등...

현재 저는 NestJS를 사용하고 있고, 프로젝트 디렉토리 구조를 간략하게 필요한 부분만 보여드리자면, 아래와 같습니다. domain 별로 디렉토리를 만들어 dto, entity, service, module 파일을 관리하고 있고, 확장성을 고려해 story 디렉토리 안의 constants.ts와 같이 domain 별로 관리하려 합니다!.

├── app.module.ts
├── domain
│   ├── common
│   │   ├── dto (폴더)
│   │   ├── entity (폴더)
│   │   └── error (폴더)
│   ├── hashtag
│   │   ├── hashtag-story.entity.ts
│   │   └── hashtag.entity.ts
│   ├── story
│   │   ├── constants.ts
│   │   ├── dto (폴더)
│   │   ├── story.entity.ts
│   │   ├── story.module.ts
│   │   └── story.service.ts
│   └── user
│       └── user.entity.ts
└── main.ts

constants.ts의 내용은 magic number를 사용하지 않기 위한 상수들을 모두 포함하고 있을 예정입니다. 아래 예시처럼 유효성 검증을 위한 값이나, 설정 선택지 등과 같은 값이 들어가겠죠 :)

export const validTimeTypes = [12, 24] as const;  
export const Title = {  
  MIN_LENGTH: 1,  
  MAX_LENGTH: 30,  
} as const;

참고


profile
한 걸음 한 걸음 쌓아가자😎

0개의 댓글