
프로젝트 진행 도중 dto 값 중 하나의 필드가 ai 혹은 user만 들어가야 되는 상황이였다. 여기서 전통적인 타입스크립트에서의 방법은 바로 enum을 사용하는 것이다.
export enum Role {
USER = 'user',
AI = 'ai',
}
export class CreateChatDto {
@IsEnum(Role)
role: Role;
}
타입스크립트에서의 enum은 편리하지만 문제가 있어 요새는 잘 쓰이지 않는다고 하는데 이유는 다음과 같다.
- 런타임 시 js 코드로 남음(컴파일 후 객체가 됨)
- 예시→ 컴파일 후
var Role; (function (Role) { Role["User"] = "user"; Role["Ai"] = "ai"; })(Role || (Role = {}));enum Role { User = 'user', Ai = 'ai', }- 이렇게 런타임에 사용될 객체 코드 생성
- 이는 번들 크기 증가, 트리쉐이킹 불가, 성능 저하로 이어질 수 있다.
- enum은 타입이면서 값이여서 혼동됨
enum은 타입과 값이 같기 때문에, 코드에서 사용 시 혼동되기 쉽다.- 이중 역할을 하기 때문에 코드 이해가 어려워지고, 테스트/리팩터링이 어려워질 수 있다.
enum Color { Red = 'red', } function getColor(c: Color) { ... } getColor(Color.Red); // ✅ 값으로 전달- enum은 자바스크립트 표준이 아니기 때문에 js 개발자와의 협업이나 외부 라이브러리 사용 시 문제가 발생할 수 있다.
- 트리쉐이킹이 잘 안됨(사용하지 않아도 코드에 남아있음)
- typeof 사용 시 불편함
따라서 좀 더 모던하게 as const + 유니온으로 구현해보자
export const ROLES = ['user', 'ai'] as const;
export type Role = (typeof ROLES)[number];
export class CreateChatDto {
@IsIn(ROLES)
role: Role;
}
이렇게 하면 컴파일 후 코드가 거의 남지 않으며(트리쉐이킹 친화적) 타입 정의와 값 정의가 동시에 가능하며 좀 더 유연하고 가볍다.
as const
배열이나 객체를 리터럴 타입으로 고정시킨다.
여기서 리터럴 타입이란 타입스크립트에서 값 그 자체를 타입으로 사용할 수 있게 하는 개념이다. 기본적인 타입보다 더 구체적이고 제한적인 타입을 명시할 수 있게 해준다. 따라서 string, number, boolean 중 하나의 구체적인 값만 허용하는 타입
let role: 'user';
role = 'user'; // ✅ OK
role = 'ai'; // ❌ Error: 'ai'은 'user' 타입이 아님
// 여기서 'user'는 값이 아닌 그 값 자체를 타입으로 본 것임
이렇게 되면 기존 string 타입은 아무 문자열이나 혀용하지만 ‘role’은 오직 ‘user’만 허용하게 되어 더욱 더 제한적이고 명시적인 타입을 제시할 수 있다.
그러니까 여기서는 ‘ai’와 ‘user’ 값들이 타입 배열으로 고정된 것이다.
그러면 dto를 정의해보자.
import { IsString, IsIn } from 'class-validator';
/** 1. Role 값을 정의하고 타입으로도 쓰기 */
export const ROLES = ['user', 'ai'] as const;
export type Role = (typeof ROLES)[number]; // "user" | "ai"
/** 2. DTO에 적용 */
export class CreateChatDto {
@IsString()
@IsIn(ROLES, { message: 'role must be either "user" or "ai"' })
role: Role;
// 예시: message 내용
@IsString()
message: string;
}
우선 앞서 설명한 as const로 ‘user’와 ‘ai’ 를 타입 배열로 고정했다.
그 뒤 type Role 에 typeof ROLES[number]를 할당해 그 배열 요소 중 하나 즉 “user”|”ai”를 의미하게 된다.
| 항목 | 설명 |
|---|---|
| 가볍고 빠름 | enum은 컴파일 시 JS 객체로 남지만, as const는 그렇지 않음 |
| 타입과 값 일치 | 한 번 정의로 타입 검사도 되고 값 검증도 가능 |
| NestJS와 호환 | @IsIn()으로 런타임 유효성 검사도 충분히 커버 가능 |
| IDE 지원 좋음 | "user" |
| 트리쉐이킹 | 불필요한 JS 코드 제거가 쉬움 (빌드 최적화에 유리) |
주의할 점
@IsEnum()데코레이터는 사용할 수 없고@IsIn()을 써야 함- 숫자 기반 enum처럼 인덱싱 접근이 필요한 경우에는 부적합
공부하면서 정리한 내용입니다. 모든 지적 감사히 받겠습니다:)
'프로그래밍 언어 > TypeScript' 카테고리의 다른 글
| [TypeScript] Bcrypt를 이용한 비밀번호 해쉬화(암호화) (0) | 2023.12.15 |
|---|---|
| [TypeScript] 타입스크립트 알아보기 (2) 함수, 인터페이스, 제네릭 (0) | 2023.08.22 |
| [TypeScript] 타입스크립트 알아보기 (1) 쓰는 이유와 타입의 종류 (1) | 2023.08.22 |
| [TS] 타입스크립트에서 Wrapper 객체 타입 피하기 (3) | 2023.05.16 |
| [TypeScript] 타입스크립트란? (0) | 2023.03.05 |