
TypeScript: Enum, union type, nebo as const?
Porovnání tří způsobů definování konstant v TypeScriptu. Kdy který použít a jaké jsou jejich výhody a nevýhody.
V TypeScriptu máte několik způsobů, jak definovat sadu konstant. Nejčastější jsou enum, union type a const assertion. Každý přístup má své výhody a nevýhody.
Přehled
| Přístup | Runtime hodnota | Tree-shaking | Iterace |
|---|---|---|---|
| enum | Ano | Ano* | Ano |
| const enum | Ne (inlinuje se) | Ano | Ne |
| Union type | Ne | - | Ne |
| as const | Ano | Ano* | Ano |
* Moderní bundlery (esbuild) dokáží hodnoty inlinovat a celý enum/objekt odstranit.
Runtime hodnota znamená, že konstanta existuje i po kompilaci do JavaScriptu — můžete ji použít pro iteraci, validaci nebo ji předat do funkce. Union type existuje jen při kompilaci a v JavaScriptu zmizí.
Tree-shaking je optimalisace, kdy build nástroj (Vite, webpack, esbuild) automaticky odstraní nepoužívaný kód z výsledného bundlu. Moderní bundlery jako esbuild dokáží enum hodnoty inlinovat a celý enum odstranit — výsledek je pak stejný jako u const enum.
Enum
TypeScript enum je speciální konstrukce, která vytváří pojmenované konstanty. Dnes je považován za legacy pattern — vznikl v době, kdy TypeScript neměl lepší alternativy. Moderní projekty preferují union types nebo as const.
enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 'pending'
}
function setStatus(status: Status) {
console.log(status);
}
setStatus(Status.Active); // 'active'
Číselný enum
Bez explicitních hodnot TypeScript přiřadí čísla od 0:
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // 'Up' (reversní mapování)
Číselné enumy mají reversní mapování — můžete získat název z hodnoty. To ale zvětšuje výstupní kód.
Co se vygeneruje
Enum se kompiluje do JavaScriptového objektu:
// TypeScript
enum Status {
Active = 'active',
Inactive = 'inactive'
}
// Vygenerovaný JavaScript
var Status;
(function (Status) {
Status["Active"] = "active";
Status["Inactive"] = "inactive";
})(Status || (Status = {}));
Vygenerovaný kód používá IIFE (Immediately Invoked Function Expression) — funkci, která se definuje a okamžitě spustí. Zápis (function() { ... })() vytvoří izolovaný scope. Konstrukce Status || (Status = {}) umožňuje rozšiřovat enum napříč více soubory (pokud Status již existuje, použije se, jinak se vytvoří prázdný objekt).
Pro číselný enum je kód ještě delší kvůli reversnímu mapování.
Výhody enum
- Srozumitelná syntaxe
- Lze iterovat přes hodnoty
- Reversní mapování (pro číselné enumy)
- Hodnoty existují za běhu
Nevýhody enum
- Generuje runtime kód
- Bez bundleru generuje IIFE wrapper (moderní bundlery jako esbuild to optimalisují)
- Není nativní JavaScript — specifické pro TypeScript
- Problémy s
isolatedModules— tato volba vtsconfig.jsonzajišťuje, že každý soubor lze kompilovat samostatně (vyžadují ji nástroje jako Babel, esbuild nebo SWC). Při zapnuté volbě nelze re-exportovat enum z jiného souboru (export { Status } from './types'), protože kompilátor neví, jestli jeStatustyp nebo hodnota.
Const enum
Const enum se úplně odstraní při kompilaci — hodnoty se inlinují přímo do kódu:
const enum Status {
Active = 'active',
Inactive = 'inactive'
}
console.log(Status.Active);
// Vygenerovaný JavaScript
console.log("active"); // Enum zmizí, hodnota se vloží přímo
Omezení const enum
- Nelze iterovat — za běhu neexistuje
- Problémy při použití z jiných balíčků
- Nefunguje s
isolatedModules: true— tato volba vyžaduje, aby každý soubor byl samostatně kompilovatelný. Const enum ale potřebuje znát hodnoty z jiného souboru v době kompilace, což není možné. - Nelze použít computed hodnoty — hodnoty musí být konstantní výrazy (literály, reference na jiné členy enum). Nelze použít např.
Math.random()nebo volání funkcí.
TypeScript tým nedoporučuje const enum v knihovnách.
Union type
Union type definuje typ jako sjednocení literálů (konkrétních hodnot jako "active" nebo 200, na rozdíl od obecných typů jako string nebo number):
type Status = 'active' | 'inactive' | 'pending';
function setStatus(status: Status) {
console.log(status);
}
setStatus('active'); // OK
setStatus('unknown'); // Chyba: Argument of type '"unknown"' is not assignable
S objektovým typem
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
interface Request {
method: HttpMethod;
url: string;
}
const req: Request = {
method: 'GET',
url: '/api/users'
};
Výhody union type
- Žádný runtime kód — existuje jen v typovém systému
- Nativní TypeScript pattern
- Funguje s
isolatedModules - Výborná podpora v IDE (autocomplete)
Nevýhody union type
- Nelze iterovat — není runtime hodnota
- Nelze získat seznam hodnot za běhu
- Při mnoha hodnotách může být nepřehledné
- Při volání funkce píšete stringy přímo v kódu (
setStatus('active')) — náchylné na překlepy a IDE autocomplete funguje až po napsání uvozovky
Poslední bod řeší as const, kde místo stringů používáte pojmenované konstanty (Status.Active).
Const assertion (as const)
Const assertion vytvoří readonly objekt nebo pole s literálními typy. Nejjednodušší je pole:
const STATUSES = ['active', 'inactive', 'pending'] as const;
type Status = typeof STATUSES[number];
// Typ: "active" | "inactive" | "pending"
// Můžete iterovat
STATUSES.forEach(status => console.log(status));
Zápis [number] znamená „typ libovolného prvku pole“ — je výrazně kratší než [keyof typeof] u objektů.
S objektem (pojmenované klíče)
Pokud chcete pojmenované konstanty jako Status.Active:
const Status = {
Active: 'active',
Inactive: 'inactive',
Pending: 'pending'
} as const;
type StatusType = typeof Status[keyof typeof Status];
// Typ: "active" | "inactive" | "pending"
setStatus(Status.Active); // Pojmenovaná konstanta
setStatus('active'); // Také funguje
Zápis typeof Status[keyof typeof Status] je krkolomný, ale dá se zjednodušit pomocí helper typu.
Co se vygeneruje
// TypeScript
const Status = {
Active: 'active',
Inactive: 'inactive'
} as const;
// Vygenerovaný JavaScript
const Status = {
Active: 'active',
Inactive: 'inactive'
};
Konstanta as const se zkompiluje do běžného objektu — žádný overhead.
Výhody as const
- Kombinuje výhody enum a union type
- Runtime hodnoty existují
- Lze iterovat
- Minimální vygenerovaný kód
- Bez bundleru generuje menší kód než enum
- Funguje s
isolatedModules - Nativní JavaScript pattern
Nevýhody as const
- Více kódu pro extrakci typu (
typeof X[keyof typeof X]) - Méně intuitivní pro začátečníky
- Stále můžete psát stringy přímo v kódu —
if (status === "active")projde, i když máteStatus.Active
Poslední bod lze řešit ESLint pravidlem zakazujícím stringové literály tam, kde existuje pojmenovaná konstanta. TypeScript sám překlepy zachytí (typ nedovolí "actve"), ale nenutí vás používat Status.Active místo "active". U union types toto řešit nelze — tam pojmenované konstanty neexistují a stringy jsou jediná možnost.
Helper pro as const
Pro jednodušší práci s as const můžete vytvořit helper:
// Helper pro získání union typu z objektu
type ValueOf<T> = T[keyof T];
const Status = {
Active: 'active',
Inactive: 'inactive',
Pending: 'pending'
} as const;
type Status = ValueOf<typeof Status>;
// "active" | "inactive" | "pending"
// Helper pro pole
type ArrayElement<T> = T extends readonly (infer U)[] ? U : never;
const ROLES = ['admin', 'user', 'guest'] as const;
type Role = ArrayElement<typeof ROLES>;
// "admin" | "user" | "guest"
Praktické porovnání
Definice
// Enum
enum ColorEnum {
Red = 'red',
Green = 'green',
Blue = 'blue'
}
// Union type
type ColorUnion = 'red' | 'green' | 'blue';
// As const
const Color = {
Red: 'red',
Green: 'green',
Blue: 'blue'
} as const;
type ColorConst = typeof Color[keyof typeof Color];
Použití
// Enum
function paintEnum(color: ColorEnum) {}
paintEnum(ColorEnum.Red);
// Union type
function paintUnion(color: ColorUnion) {}
paintUnion('red');
// As const
function paintConst(color: ColorConst) {}
paintConst(Color.Red);
paintConst('red'); // Také funguje
Iterace
// Enum — pozor u číselných enumů (vrací i klíče)
Object.values(ColorEnum).forEach(color => console.log(color));
// Union type — nelze
// ColorUnion.forEach... // Chyba - typ neexistuje za běhu
// As const — funguje
Object.values(Color).forEach(color => console.log(color));
Kdy co použít
Použijte union type když:
- Nepotřebujete runtime hodnoty
- Máte jednoduchý seznam možností
- Chcete co nejmenší bundle
type Size = 'sm' | 'md' | 'lg' | 'xl';
type Theme = 'light' | 'dark';
type HttpStatus = 200 | 201 | 400 | 404 | 500;
Použijte as const když:
- Potřebujete runtime hodnoty i typy
- Chcete iterovat přes hodnoty
- Vytváříte knihovnu
- Máte
isolatedModules: true
const API_ENDPOINTS = {
Users: '/api/users',
Posts: '/api/posts',
Comments: '/api/comments'
} as const;
type Endpoint = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS];
// Můžete iterovat i typovat
Object.entries(API_ENDPOINTS).forEach(([name, url]) => {
console.log(`${name}: ${url}`);
});
Použijte enum když:
- Pracujete s existujícím kódem, který enumy používá
- Potřebujete reversní mapování (číselné enumy)
- Tým je na enumy zvyklý
Vyhněte se const enum
Const enum má příliš mnoho problémů. Použijte raději as const.
Problém s porovnáváním stringů
Častý problém v kódu je použití stringů přímo při porovnávání:
// "Magic string" - string přímo v kódu
if (status === "active") {
// ...
}
// Lepší - pojmenovaná konstanta
if (status === Status.Active) {
// ...
}
Stringy přímo v kódu (tzv. magic strings) jsou problematické:
- Náchylné na překlepy
- Těžší refaktoring — při přejmenování musíte hledat všechny výskyty
- Žádný autocomplete při psaní
Co TypeScript zachytí
Pokud máte správně definovaný typ, TypeScript zachytí překlepy:
type Status = 'active' | 'inactive';
function check(status: Status) {
if (status === "actve") { } // ✗ Chyba - překlep
if (status === "unknown") { } // ✗ Chyba - není v typu
if (status === "active") { } // ✓ OK
}
Co TypeScript nezachytí
TypeScript nevynucuje použití pojmenovaných konstant. I když máte Status.Active, můžete stále psát string přímo:
const Status = { Active: 'active', Inactive: 'inactive' } as const;
type StatusType = typeof Status[keyof typeof Status];
function check(status: StatusType) {
if (status === Status.Active) { } // ✓ Pojmenovaná konstanta
if (status === "active") { } // ✓ Také projde - TypeScript neprotestuje
}
Jak to řeší jednotlivé přístupy
| Přístup | Zachytí překlepy | Vynucuje konstanty |
|---|---|---|
| enum | ✓ | ✓ — "active" neprojde |
| union type | ✓ | ✗ — stringy jsou jediná možnost |
| as const | ✓ | ✗ — stringy i konstanty projdou |
Pokud potřebujete striktně vynucovat použití konstant, enum je jediná možnost. Pro většinu projektů ale stačí, že TypeScript zachytí překlepy.
Migrace z enum na as const
Pokud chcete migrovat existující enum:
// Původní enum
enum OldStatus {
Active = 'active',
Inactive = 'inactive'
}
// Nový as const
const Status = {
Active: 'active',
Inactive: 'inactive'
} as const;
type Status = typeof Status[keyof typeof Status];
// Použití zůstává téměř stejné
// OldStatus.Active → Status.Active
Ideální řešení neexistuje
Každý přístup má kompromisy:
| Požadavek | enum | union | as const |
|---|---|---|---|
| Runtime hodnoty | ✓ | ✗ | ✓ |
| Pojmenované konstanty | ✓ | ✗ | ✓ |
| Vynucení konstant (ne stringů) | ✓ | ✗ | ✗ |
| Žádný runtime overhead | ✗ | ✓ | ✓ |
| Funguje s isolatedModules | ⚠️ | ✓ | ✓ |
| Jednoduchý zápis | ✓ | ✓ | ✗ |
Nejbližší k ideálu je as const — má většinu výhod. Jediná skutečná nevýhoda je, že nevynucuje použití pojmenovaných konstant místo stringů. Pro většinu projektů to není problém, protože TypeScript zachytí překlepy.
Závěr
- Union type — pro jednoduché případy bez runtime hodnot
- As const — pro většinu případů s runtime hodnotami
- Enum — pouze pokud máte dobrý důvod
- Const enum — vyhněte se
Moderní TypeScript projekty preferují as const nebo union types. Enumy jsou stále validní volba, ale přinášejí zbytečný runtime overhead.
Související články
Jak vkládat 3D objekty na web pomocí Three.js
Které formáty použít, jak vytvářet modely pomocí AI a kdy raději použít obrázek nebo video.
JavaScript null a undefined
Rozdíly mezi null a undefined v JavaScriptu, kdy je používat a jak se vyhnout běžným chybám.
Sleep v JavaScriptu
Jak implementovat sleep/delay funkcionalitu v JavaScriptu pomocí Promise a async/await