Preskúmajte výkonné alternatívy TypeScriptu k enums: const assertions a union types. Zistite, kedy použiť ktorú pre robustný a udržiavateľný kód.
Mimo Enums: TypeScript Const Assertions vs. Union Types
Vo svete staticky typovaného JavaScriptu s TypeScriptom boli enums dlho obľúbenou voľbou na reprezentáciu pevnej množiny pomenovaných konštánt. Ponúkajú jasný a čitateľný spôsob, ako definovať kolekciu súvisiacich hodnôt. Ako však projekty rastú a vyvíjajú sa, vývojári často hľadajú flexibilnejšie a niekedy aj výkonnejšie alternatívy. Dvaja silní konkurenti, ktorí sa často objavujú, sú const assertions a union types. Tento príspevok sa zaoberá nuansami používania týchto alternatív k tradičným enums, poskytuje praktické príklady a usmerňuje vás pri výbere tej správnej.
Pochopenie tradičných TypeScript Enums
Predtým, ako preskúmame alternatívy, je nevyhnutné pevne pochopiť, ako fungujú štandardné TypeScript enums. Enums vám umožňujú definovať množinu pomenovaných numerických alebo reťazcových konštánt. Môžu byť numerické (predvolené) alebo reťazcové.
Numerické Enums
V predvolenom nastavení sú členom enum priradené numerické hodnoty začínajúce od 0.
enum DirectionNumeric {
Up,
Down,
Left,
Right
}
let myDirection: DirectionNumeric = DirectionNumeric.Up;
console.log(myDirection); // Output: 0
Môžete tiež explicitne priradiť numerické hodnoty.
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500
}
let responseStatus: StatusCode = StatusCode.Success;
console.log(responseStatus); // Output: 200
Reťazcové Enums
Reťazcové enums sú často preferované pre ich vylepšené ladenie, pretože názvy členov sú zachované v skompilovanom JavaScriptovom kóde.
enum ColorString {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
let favoriteColor: ColorString = ColorString.Blue;
console.log(favoriteColor); // Output: "BLUE"
Režia Enums
Zatiaľ čo enums sú pohodlné, prichádzajú s miernou réžiou. Pri kompilácii do JavaScriptu sa TypeScript enums transformujú na objekty, ktoré majú často reverzné mapovania (napr. mapovanie numerickej hodnoty späť na názov enum). To môže byť užitočné, ale tiež to prispieva k veľkosti balíka a nemusí to byť vždy potrebné.
Zvážte tento jednoduchý reťazcový enum:
enum Status {
Pending = "PENDING",
Processing = "PROCESSING",
Completed = "COMPLETED"
}
V jazyku JavaScript by sa to mohlo stať niečím takýmto:
var Status;
(function (Status) {
Status["Pending"] = "PENDING";
Status["Processing"] = "PROCESSING";
Status["Completed"] = "COMPLETED";
})(Status || (Status = {}));
Pre jednoduché sady konštánt len na čítanie môže byť tento generovaný kód trochu prehnaný.
Alternatíva 1: Const Assertions
Const assertions sú výkonnou funkciou TypeScriptu, ktorá vám umožňuje povedať kompilátoru, aby pre hodnotu odvodil najšpecifickejší možný typ. Ak sa používajú s poľami alebo objektmi, ktoré majú reprezentovať pevnú množinu hodnôt, môžu slúžiť ako odľahčená alternatíva k enums.
Const Assertions s poľami
Môžete vytvoriť pole reťazcových literálov a potom použiť const assertion, aby bol jeho typ nemenný a jeho prvky literálové typy.
const statusArray = ["PENDING", "PROCESSING", "COMPLETED"] as const;
type StatusType = typeof statusArray[number];
let currentStatus: StatusType = "PROCESSING";
// currentStatus = "FAILED"; // Error: Type '"FAILED"' is not assignable to type 'StatusType'.
function processStatus(status: StatusType) {
console.log(`Processing status: ${status}`);
}
processStatus("COMPLETED");
Poďme si rozobrať, čo sa tu deje:
as const: Toto assertion hovorí TypeScriptu, aby s poľom zaobchádzal ako s poľom len na čítanie a odvodil pre jeho prvky najšpecifickejšie literálové typy. Takže namiesto `string[]` sa typ stane `readonly ["PENDING", "PROCESSING", "COMPLETED"]`.typeof statusArray[number]: Toto je mapovaný typ. Iteruje cez všetky indexystatusArraya extrahuje ich literálové typy. Indexový podpisnumberv podstate hovorí "daj mi typ akéhokoľvek prvku v tomto poli." Výsledkom je union type:"PENDING" | "PROCESSING" | "COMPLETED".
Tento prístup poskytuje typovú bezpečnosť podobnú reťazcovým enums, ale generuje minimálny JavaScript. Samotný statusArray zostáva poľom reťazcov v jazyku JavaScript.
Const Assertions s objektmi
Const assertions sú ešte výkonnejšie, keď sa aplikujú na objekty. Môžete definovať objekt, kde kľúče reprezentujú vaše pomenované konštanty a hodnoty sú literálové reťazce alebo čísla.
const userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
} as const;
type UserRole = typeof userRoles[keyof typeof userRoles];
let currentUserRole: UserRole = "EDITOR";
// currentUserRole = "GUEST"; // Error: Type '"GUEST"' is not assignable to type 'UserRole'.
function displayRole(role: UserRole) {
console.log(`User role is: ${role}`);
}
displayRole(userRoles.Admin); // Valid
displayRole("EDITOR"); // Valid
V tomto príklade objektu:
as const: Toto assertion robí celý objekt len na čítanie. Dôležitejšie je, že pre všetky hodnoty vlastností odvodzuje literálové typy (napr."ADMIN"namiestostring) a samotné vlastnosti robí len na čítanie.keyof typeof userRoles: Tento výraz vedie k únii kľúčov objektuuserRoles, čo je"Admin" | "Editor" | "Viewer".typeof userRoles[keyof typeof userRoles]: Toto je lookup type. Používa úniu kľúčov na vyhľadanie zodpovedajúcich hodnôt v typeuserRoles. Výsledkom je únia hodnôt:"ADMIN" | "EDITOR" | "VIEWER", čo je náš požadovaný typ pre roly.
Výstup JavaScriptu pre userRoles bude obyčajný JavaScriptový objekt:
var userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
};
To je výrazne ľahšie ako typický enum.
Kedy použiť Const Assertions
- Konštanty len na čítanie: Keď potrebujete pevnú množinu reťazcových alebo číselných literálov, ktoré by sa nemali meniť za behu.
- Minimálny výstup JavaScriptu: Ak sa obávate o veľkosť balíka a chcete najvýkonnejšiu reprezentáciu konštánt za behu.
- Štruktúra podobná objektu: Ak preferujete čitateľnosť párov kľúč-hodnota, podobne ako by ste štruktúrovali údaje alebo konfiguráciu.
- Sady založené na reťazcoch: Zvlášť užitočné na reprezentáciu stavov, typov alebo kategórií, ktoré sa najlepšie identifikujú pomocou popisných reťazcov.
Alternatíva 2: Union Types
Union types vám umožňujú deklarovať, že premenná môže obsahovať hodnotu jedného z niekoľkých typov. V kombinácii s literálovými typmi (reťazcové, číselné, booleovské literály) tvoria výkonný spôsob, ako definovať množinu povolených hodnôt bez potreby explicitnej deklarácie konštanty pre samotnú množinu.
Union Types s reťazcovými literálmi
Môžete priamo definovať úniu reťazcových literálov.
type TrafficLightColor = "RED" | "YELLOW" | "GREEN";
let currentLight: TrafficLightColor = "YELLOW";
// currentLight = "BLUE"; // Error: Type '"BLUE"' is not assignable to type 'TrafficLightColor'.
function changeLight(color: TrafficLightColor) {
console.log(`Changing light to: ${color}`);
}
changeLight("RED");
// changeLight("REDDY"); // Error
Toto je najpriamejší a často najstručnejší spôsob, ako definovať množinu povolených reťazcových hodnôt.
Union Types s numerickými literálmi
Podobne môžete použiť numerické literály.
type HttpStatusCode = 200 | 400 | 404 | 500;
let responseCode: HttpStatusCode = 404;
// responseCode = 201; // Error: Type '201' is not assignable to type 'HttpStatusCode'.
function handleResponse(code: HttpStatusCode) {
if (code === 200) {
console.log("Success!");
} else {
console.log(`Error code: ${code}`);
}
}
handleResponse(500);
Kedy použiť Union Types
- Jednoduché, priame množiny: Keď je množina povolených hodnôt malá, jasná a nevyžaduje popisné kľúče mimo samotných hodnôt.
- Implicitné konštanty: Keď nepotrebujete odkazovať na pomenovanú konštantu pre samotnú množinu, ale skôr priamo použiť literálové hodnoty.
- Maximálna stručnosť: Pre priamočiare scenáre, kde sa definovanie vyhradeného objektu alebo poľa javí ako prehnané.
- Parametre funkcií/vrátené typy: Vynikajúce na definovanie presnej množiny prijateľných reťazcových alebo číselných vstupov/výstupov pre funkcie.
Porovnanie Enums, Const Assertions a Union Types
Poďme zhrnúť kľúčové rozdiely a prípady použitia:
Správanie za behu
- Enums: Generujú JavaScriptové objekty, potenciálne s reverznými mapovaniami.
- Const Assertions (Polia/Objekty): Generujú obyčajné JavaScriptové polia alebo objekty. Informácie o type sa vymažú za behu, ale dátová štruktúra zostáva.
- Union Types (s literálmi): Žiadna reprezentácia za behu pre samotnú úniu. Hodnoty sú len literály. Kontrola typov prebieha čisto počas kompilácie.
Čitateľnosť a expresívnosť
- Enums: Vysoká čitateľnosť, najmä s popisnými názvami. Môžu byť rozsiahlejšie.
- Const Assertions (Objekty): Dobrá čitateľnosť prostredníctvom párov kľúč-hodnota, napodobňujúcich konfigurácie alebo nastavenia.
- Const Assertions (Polia): Menej čitateľné na reprezentáciu pomenovaných konštánt, viac pre usporiadaný zoznam hodnôt.
- Union Types: Veľmi stručné. Čitateľnosť závisí od jasnosti samotných literálových hodnôt.
Typová bezpečnosť
- Všetky tri prístupy ponúkajú silnú typovú bezpečnosť. Zabezpečujú, že premenným je možné priradiť alebo prenášať do funkcií iba platné, preddefinované hodnoty.
Veľkosť balíka
- Enums: Všeobecne najväčšie kvôli generovaným JavaScriptovým objektom.
- Const Assertions: Menšie ako enums, pretože vytvárajú obyčajné dátové štruktúry.
- Union Types: Najmenšie, pretože negenerujú žiadnu špecifickú dátovú štruktúru za behu pre samotný typ, ale spoliehajú sa iba na literálové hodnoty.
Use Cases Matrix
Tu je rýchly sprievodca:
| Funkcia | TypeScript Enum | Const Assertion (Objekt) | Const Assertion (Pole) | Union Type (Literály) |
|---|---|---|---|---|
| Výstup za behu | JS Objekt (s reverzným mapovaním) | Obyčajný JS Objekt | Obyčajné JS Pole | Žiadny (iba literálové hodnoty) |
| Čitateľnosť (Pomenované konštanty) | Vysoká | Vysoká | Stredná | Nízka (hodnoty sú názvy) |
| Veľkosť balíka | Najväčšia | Stredná | Stredná | Najmenšia |
| Flexibilita | Dobrá | Dobrá | Dobrá | Výborná (pre jednoduché množiny) |
| Bežné použitie | Stavy, stavové kódy, kategórie | Konfigurácia, definície rolí, prepínače funkcií | Usporiadané zoznamy nemenných hodnôt | Parametre funkcií, jednoduché obmedzené hodnoty |
Praktické príklady a osvedčené postupy
Príklad 1: Reprezentácia stavových kódov API
Enum:
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Pending = "PENDING"
}
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
Const Assertion (Objekt):
const apiStatusCodes = {
SUCCESS: "SUCCESS",
ERROR: "ERROR",
PENDING: "PENDING"
} as const;
type ApiStatus = typeof apiStatusCodes[keyof typeof apiStatusCodes];
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
Union Type:
type ApiStatus = "SUCCESS" | "ERROR" | "PENDING";
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
Odporúčanie: Pre tento scenár je často najstručnejší a najefektívnejší union type. Samotné literálové hodnoty sú dostatočne popisné. Ak by ste potrebovali priradiť ku každému stavu ďalšie metadáta (napr. užívateľsky prívetivú správu), objekt const assertion by bol lepšou voľbou.
Príklad 2: Definícia používateľských rolí
Enum:
enum UserRoleEnum {
Admin = "ADMIN",
Moderator = "MODERATOR",
User = "USER"
}
function getUserPermissions(role: UserRoleEnum) {
// ... logic ...
}
Const Assertion (Objekt):
const userRolesObject = {
Admin: "ADMIN",
Moderator: "MODERATOR",
User: "USER"
} as const;
type UserRole = typeof userRolesObject[keyof typeof userRolesObject];
function getUserPermissions(role: UserRole) {
// ... logic ...
}
Union Type:
type UserRole = "ADMIN" | "MODERATOR" | "USER";
function getUserPermissions(role: UserRole) {
// ... logic ...
}
Odporúčanie: Objekt const assertion tu vytvára dobrú rovnováhu. Poskytuje jasné páry kľúč-hodnota (napr. userRolesObject.Admin), ktoré môžu zlepšiť čitateľnosť pri odkazovaní na roly a zároveň sú výkonné. Union type je tiež veľmi silný konkurent, ak sú priame reťazcové literály dostatočné.
Príklad 3: Reprezentácia možností konfigurácie
Predstavte si konfiguračný objekt pre globálnu aplikáciu, ktorá môže mať rôzne témy.
Enum:
enum Theme {
Light = "light",
Dark = "dark",
System = "system"
}
interface AppConfig {
theme: Theme;
// ... other config options ...
}
Const Assertion (Objekt):
const themes = {
Light: "light",
Dark: "dark",
System: "system"
} as const;
type Theme = typeof themes[keyof typeof themes];
interface AppConfig {
theme: Theme;
// ... other config options ...
}
Union Type:
type Theme = "light" | "dark" | "system";
interface AppConfig {
theme: Theme;
// ... other config options ...
}
Odporúčanie: Pre nastavenia konfigurácie, ako sú témy, je objekt const assertion často ideálny. Jasne definuje dostupné možnosti a ich zodpovedajúce reťazcové hodnoty. Kľúče (Light, Dark, System) sú popisné a priamo sa mapujú na hodnoty, vďaka čomu je konfiguračný kód veľmi zrozumiteľný.
Výber správneho nástroja pre danú prácu
Rozhodnutie medzi TypeScript enums, const assertions a union types nie je vždy čiernobiele. Často ide o kompromis medzi výkonom za behu, veľkosťou balíka a čitateľnosťou/expresívnosťou kódu.
- Rozhodnite sa pre Union Types, keď potrebujete jednoduchú, obmedzenú množinu reťazcových alebo číselných literálov a požaduje sa maximálna stručnosť. Sú vynikajúce pre podpisy funkcií a základné obmedzenia hodnôt.
- Rozhodnite sa pre Const Assertions (s objektmi), keď chcete štruktúrovanejší a čitateľnejší spôsob definovania pomenovaných konštánt, podobne ako enum, ale s výrazne menšou réžiou za behu. To je skvelé pre konfiguráciu, roly alebo akúkoľvek množinu, kde kľúče pridávajú významný význam.
- Rozhodnite sa pre Const Assertions (s poľami), keď jednoducho potrebujete nemenný usporiadaný zoznam hodnôt a priamy prístup cez index je dôležitejší ako pomenované kľúče.
- Zvážte TypeScript Enums, keď potrebujete ich špecifické funkcie, ako je reverzné mapovanie (hoci je to v modernom vývoji menej bežné), alebo ak má váš tím silnú preferenciu a vplyv na výkon je pre váš projekt zanedbateľný.
V mnohých moderných projektoch TypeScript sa nájde sklon k const assertions a union types oproti tradičným enums, najmä pre reťazcové konštanty, kvôli ich lepším charakteristikám výkonu a často jednoduchšiemu výstupu JavaScriptu.
Globálne aspekty
Pri vývoji aplikácií pre globálne publikum sú dôležité konzistentné a predvídateľné definície konštánt. Voľby, o ktorých sme diskutovali (enums, const assertions, union types), prispievajú ku tejto konzistencii tým, že presadzujú typovú bezpečnosť v rôznych prostrediach a medzi vývojármi.
- Konzistencia: Bez ohľadu na zvolený spôsob je kľúčová konzistencia v rámci vášho projektu. Ak sa rozhodnete použiť objekty const assertion pre roly, držte sa tohto vzoru v celom kóde.
- Internacionalizácia (i18n): Pri definovaní štítkov alebo správ, ktoré budú internacionalizované, použite tieto štruktúry bezpečné pre typy, aby ste zaistili, že sa použijú iba platné kľúče alebo identifikátory. Skutočné preložené reťazce sa budú spravovať samostatne prostredníctvom knižníc i18n. Napríklad, ak máte pole `status`, ktoré môže byť "PENDING", "PROCESSING", "COMPLETED", vaša knižnica i18n by mapovala tieto interné identifikátory na lokalizovaný zobrazovaný text.
- Časové zóny a meny: Hoci to priamo nesúvisí s enums, pamätajte, že pri práci s hodnotami, ako sú dátumy, časy alebo meny, môže systém typov TypeScriptu pomôcť presadiť správne použitie, ale na presné globálne spracovanie sú zvyčajne potrebné externé knižnice. Napríklad, typ únie `Currency` by mohol byť definovaný ako `"USD" | "EUR" | "GBP"`, ale skutočná logika konverzie vyžaduje špecializované nástroje.
Záver
TypeScript poskytuje bohatú sadu nástrojov na správu konštánt. Zatiaľ čo enums nám dobre slúžili, const assertions a union types ponúkajú presvedčivé, často výkonnejšie alternatívy. Pochopením ich rozdielov a výberom správneho prístupu na základe vašich špecifických potrieb – či už ide o výkon, čitateľnosť alebo stručnosť – môžete písať robustnejší, udržiavateľnejší a efektívnejší kód TypeScript, ktorý sa škáluje globálne.
Osvojenie si týchto alternatív môže viesť k menším veľkostiam balíkov, rýchlejším aplikáciám a predvídateľnejšiemu vývojárskemu zážitku pre váš medzinárodný tím.