enum
μ tree-shaking, memory-leak λ¬Έμ κ° μκ³ , μ¬λ¬ enumμ νλμ enumμΌλ‘ ν©μΉ μ μλ€class-validator
μμIsEnum
μ μ°λ €λ©΄, νμ μ΄ μλ κ°μ²΄κ° νμνλ€literal
κ³ΌReadonly<Record<K, V>>
μ νΈ νμ μ νμ©ν΄ λ¬Έμ λ₯Ό ν΄κ²°νλ€
enum
μ λΉμΈκ³ λΆνΈνλ€!λ€λ₯Έ μΈμ΄λ€κ³Ό λ§μ°¬κ°μ§λ‘ TypeScript
λ μ΄κ±°ν νμ
μΌλ‘ enum
μ μ 곡νλ€
ν¨μ μΈμλ‘ νΉμ stringλ§ λ€μ΄μμΌ νλ κ²½μ° λ± μ μ©νκ² μΈ μ μλ€
κ·Έλ¬λ Line κΈ°μ λΈλ‘κ·Έ('TypeScript enumμ μ¬μ©νμ§ μλ κ² μ’μ μ΄μ λ₯Ό Tree-shaking κ΄μ μμ μκ°ν©λλ€.')μμ μ μ€λͺ νλ κ²μ²λΌ, μμ§ν enum νμ μ tree-shakingμ΄ μλκ³ λ©λͺ¨λ¦¬ λλΉκΉμ§ μ΄μ΄μ§ μ μλ€.
무μλ³΄λ€ κ°μΈμ μΌλ‘ κ°μ₯ λΆνΈνκ² λκ»΄μ§λ κ²μ λ€λ₯Έ νμ
λ€κ³Όλ λ€λ₯΄κ², enumμ union
μΌλ‘ μ¬λ¬ enumμ νλλ‘ ν©μΉ μ μλ€λ μ μ΄λ€
μλ μμ μ½λλ μ€λ¬΄μμ ꡬν μ€μΈ API νμ μΌλΆλ₯Ό μ‘°κΈ μμ ν΄μ κ°μ Έμ¨ κ²μ΄κ³ , μ νμ μΈ enumμ κ°μ Έμλ€
enum PeriodA {
DAILY = 'DAILY',
MONTHLY = 'MONTHLY'
}
enum PeriodCommon {
WEEKLY = 'WEEKLY',
QUARTERLY = 'QUARTERLY',
YEARLY = 'YEARLY'
}
Union Typeμ μ°λ λλΆλΆμ μλμ²λΌ A νμ μΌμλ μκ³ , B νμ μΌ μλ μμ λμ΄λ€
type StringOrNumber = string | number;
const strOrNumberLogger = (what: StringOrNumber) => console.log(what);
strOrNumberLogger('asdf');
strOrNumberLogger(123123);
κ·Έλ¬λ μ΄ λμ enumμ κ·Έλ μ§ μλ€
PeriodA
μ PeriodCommon
λ₯Ό ν©μΉ Periods
λΌλ enumμ λ§λ€κ³ μΆμ§λ§ κ·Έ κ³Όμ μ μ½μ§ μλ€
μλμ²λΌ νμ Unionμ νλ©΄ λΉμ₯μ μλ¬κ° λμ§ μμ§λ§, μ€μ§μ μΌλ‘λ μ¬μ©ν μ μλ νμ μ΄ λλ€
type Periods = PeriodA | PeriodCommon;
const periodLogger = (period: Periods) => console.log(period);
periodLogger('DAILY'); // error
Intersectionμ λλμ± μλλ€
λ μ¬μ΄μ 곡ν΅μ μ΄ μκΈ° λλ¬Έμ never
νμ
μ΄ λλ€
type Periods = PeriodA & PeriodCommon; // -> never
const periodLogger = (period: Periods) => console.log(period);
periodLogger('DAILY'); // error
νμ assertionμΌλ‘ μλ¬λ₯Ό μλΌ μλ μλλ°..μ무 μλ―Έ μλ νμ μ μΈμ΄ λμ΄ λ²λ¦°λ€
class-validator
μ IsEnum
μ μ°λ €λ©΄ κ°μ²΄κ° νμνλ€!κ°μ₯ μ νΈν건 λ¨μν literal
'λ§' μ°λ κ±°λ€
ν μ€ κΈ°μ λΈλ‘κ·Έ Template Literal Typesλ‘ νμ μμ νκ² μ½λ©νκΈ°μμ μκ°νλ κ²μ²λΌ,
TypeScriptμλ λ¬Έμμ΄μ κ°μ§κ³ λ€μν νμ μ λ§λ€μ΄ λΌ μ μλ μ¬λ―Έμλ(?) κΈ°λ₯μ΄ μλ€
λ€λ§ μμμ μμλ‘ κ°μ Έμ¨ Periods
λ λ¨μν ν¨μ μΈμμ νμ
λ§ μΆλ‘ νλλ° μ°λ κ²μ΄ μλλΌ,
μλμ κ°μ΄ class-validator
λ₯Ό νμ©ν΄ API μμ² μ ν¨μ± κ²μ¬μλ μ¬μ©λ μ μμ΄μΌνλ€
export class GetChartDto {
@IsNotEmpty()
@IsEnum(Periods)
period: PeriodNames;
}
λΉμ°ν κ±°μ§λ§, μλμ κ°μ΄ typeμ ν¨μ μΈμλ‘ λ£μ μλ μλ€
μ λΌμΈ λΈλ‘κ·Έμμ κ°μ₯ μΆμ²νλ λ°©λ²μ κ°μ²΄λ₯Ό κ°μ§κ³ Union νμ μ λ§λλ κ²μ΄λ€
enumμΌλ‘ union λ§λ€ λ°©λ²μ ꡬκΈλ§('typescript enum union'
)νλ©΄ λμκ² κ°μ₯ λ¨Όμ λ¨λ κΈμΈ
μ κ·νλμ enum type λμ union typeμΌλ‘ λ³κ²½νκΈ°μμλ κ·Έ λ°©μμ κ°μ νμ¬ μ¬μ©ν κ²½νμ 보μ¬μ£Όκ³ μλ€
μ¬κΈ°μ ν΅μ¬μ μλμ κ°λ€
const READONLY_κ°μ²΄ = κ°μ²΄ as const;
type enumLike = keyof READONLY_κ°μ²΄[keyof typeof READONLY_κ°μ²΄]
μ§§κ³ μ μ©ν μ½λμ§λ§, λΆμ‘±ν λμκ²λ μ΄κ² λ μλ―Ένλμ§ νλ²μ νμ νκΈ°κ° μ‘°κΈ μ΄λ ΅λ€κ³ λκ»΄μ‘λ€
λ€λ§ Readonly
νμ
μ μ¬μ©νλ€λ μ μμ ννΈλ₯Ό μ»μ μ μμλ€
(μ΄κ² κΌ νμν 건μ§λ μμ§ ν μ€νΈλ₯Ό μν΄λ΄€λ€...)
κ²°λ‘ μ μλμ κ°λ€
// periods.ts
type ReadonlyRecord<K extends string, V> = Readonly<Record<K, V>>;
export type PeriodANames = 'DAILY' | 'MONTHLY';
export const PeriodA: ReadonlyRecord<PeriodANames, PeriodANames> = {
DAILY: 'DAILY',
MONTHLY: 'MONTHLY',
};
type PeriodCommonNames = 'WEEKLY' | 'QUARTERLY' | 'YEARLY';
const PeriodCommon: ReadonlyRecord<PeriodCommonNames, PeriodCommonNames> = { /** */ }
export type PeriodNames = PeriodANames | PeriodCommonNames;
export const Periods = { ...PeriodA, ...PeriodCommon };
ReadonlyRecord
μ΄ λΆλΆμ λ°λμ νμν λΆλΆμ μλκ³ , κ°μΈμ μΌλ‘ κ°μ²΄λ₯Ό Reaonly
λ‘ λ§λλ κ²½μ°κ° λ§μ λ°λ‘ μ μΈνλ€
μ€λ¬΄ μ½λμμλ μ’λ νΈν μ½λ©μ μν΄ λΌλ₯Ό λΆλ €μ μλμ κ°μ΄ μ μΈνλ€
κ°μ²΄ key
, value
κ° λͺ¨λ string
μΈ κ²½μ°μλ κΈ°λ³Έκ° λλΆμ μ λ€λ¦μ μλ΅ν μ μκ³ ,
string
μ μλμ§λ§ key
, value
κ° λμΌν νμ
μΈ κ²½μ°λ νλλ§ μ μ΄μ£Όλ©΄ λλ€
export type ReadonlyRecord<P extends string = string, Q = P> = Readonly<Record<P, Q>>;
export const PeriodA: ReadonlyRecord<PeriodANames> = { /** */ }
literal
νμ
; PeriodNames
, _RestPeriodNames
λ¨μν λ¬Έμμ΄ literal νμ μ΄λ€
Unionμ΄λΌλ μλ―Έμ λ§κ², κ΄μ¬μ¬μ λ°λΌ λΆλ¦¬λ νμ λ€μ νλλ‘ λ¬Άλ κ²μ΄ μ½λ€
keyof typeof κ°μ²΄
λ₯Ό λμ νκΈ° μν΄ νμ
μ νλνλ λ μ¨μΌνλ€λ κ²μ΄ λ¨μ μ΄κΈ°λ νμ§λ§,
μλ κ°μ²΄λ₯Ό μμ±ν λ μλμμ±μ΄ λκΈ° λλ¬Έμ ν¬κ² λΆνΈν¨μ λλΌμ§ μμμ μλ€
μ€νλ € νλμ νμ μ λ무 λ§μ μμ±μ΄ μμ΄μ νμ΄ν μκ°μ΄ μ€λ 걸릴 μ λλΌλ©΄, νμ μ μΆ©λΆν λΆλ¦¬νμ§ λͺ»ν κ² μλμ§ κ²ν ν΄λ΄μΌ ν κ² κ°λ€
λ ν μ€ λΈλ‘κ·Έμμ μκ°ν κ²μ²λΌ, template-literal
νμ
λ€μ μ‘°ν©ν μλ‘μ΄ νμ
λ€μ μ μΈνκΈ° λ§€μ° νΈλ¦¬ν΄μ§λ€
export type MarketNames = 'domestic' | 'overseas';
export type CategoryNames = 'index' | 'stock';
export type DetailChartTypeNames = `${MarketNames}-${CategoryNames}`;
export const DetailChartTypes: ReadonlyRecord<DetailChartTypeNames> = {
'domestic-index': 'domestic-index',
'domestic-stock': 'domestic-stock',
'overseas-index': 'overseas-index',
'overseas-stock': 'overseas-stock',
};
class-validator IsEnum
μ νμ©// get-chart-dto.ts
export class GetChartDto {
@IsNotEmpty()
@IsEnum(Periods)
period: PeriodNames;
}
literal
μ νμ©νμ¬ μμ ν(type-safe) Readonly
κ°μ²΄λ₯Ό λ§λ€κ³ , class-validator
μμ νμ©νλ€
class-validator
κ° μλλλΌλ, κΈ°μ‘΄ enum
μ¬μ©νλ― Periods.DAILY
μ κ°μ΄ μ¬μ©ν μλ μλ€
JSλ‘ Transpile λμμλ, λ¨μ κ°μ²΄ 리ν°λ΄μ΄κΈ° λλ¬Έμ λΌμΈ λΈλ‘κ·Έλλ‘λΌλ©΄ tree-shakingλ κ°λ₯νλ€
κ΄λ ¨ 리μμ€λ€μ λ§μ΄ μ 곡ν΄μ£Όμ μ μ λ§ λμμ΄ λ§μ΄ λμμ΄μ.
κ·Έλ°λ°, λ°±μλμμ νΈλ¦¬μμ΄νΉ κ΄μ μμμ enumνμ μ μ¬μ©νμ§ μμμΌ ν μ΄μ κ° μμκΉμ?