Udforsk stærke TypeScript-alternativer til enums: const assertions og union typer. Lær hvornår du skal bruge hver især for robust, vedligeholdelsesvenlig kode.
Ud over Enums: TypeScript Const Assertions vs. Union Typer
I en verden af statisk typet JavaScript med TypeScript har enums længe været et foretrukket valg til at repræsentere et fast sæt navngivne konstanter. De tilbyder en klar og læselig måde at definere en samling af relaterede værdier på. Men efterhånden som projekter vokser og udvikler sig, søger udviklere ofte mere fleksible og nogle gange mere performante alternativer. To stærke kandidater, der ofte dukker op, er const assertions og union typer. Dette indlæg dykker ned i nuancerne ved at bruge disse alternativer til traditionelle enums, giver praktiske eksempler og vejleder dig i, hvornår du skal vælge hvilken.
ForstĂĄelse af Traditionelle TypeScript Enums
Før vi udforsker alternativerne, er det vigtigt at have en solid forståelse af, hvordan standard TypeScript enums fungerer. Enums giver dig mulighed for at definere et sæt navngivne numeriske eller strengkonstanter. De kan være numeriske (standard) eller strengbaserede.
Numeriske Enums
Som standard tildeles enum-medlemmer numeriske værdier startende fra 0.
enum DirectionNumeric {
Up,
Down,
Left,
Right
}
let myDirection: DirectionNumeric = DirectionNumeric.Up;
console.log(myDirection); // Output: 0
Du kan også eksplicit tildele numeriske værdier.
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500
}
let responseStatus: StatusCode = StatusCode.Success;
console.log(responseStatus); // Output: 200
Streng Enums
Streng enums foretrækkes ofte på grund af deres forbedrede fejlfindingsoplevelse, da medlemsnavnene bevares i den kompilerede JavaScript.
enum ColorString {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
let favoriteColor: ColorString = ColorString.Blue;
console.log(favoriteColor); // Output: "BLUE"
Enums' Overhead
Selvom enums er bekvemme, kommer de med en smule overhead. Når de kompileres til JavaScript, omdannes TypeScript enums til objekter, der ofte har omvendte mappinger (f.eks. mapping af den numeriske værdi tilbage til enum-navnet). Dette kan være nyttigt, men bidrager også til pakkestørrelsen og er måske ikke altid nødvendigt.
Overvej denne simple streng enum:
enum Status {
Pending = "PENDING",
Processing = "PROCESSING",
Completed = "COMPLETED"
}
I JavaScript kan dette blive noget i stil med:
var Status;
(function (Status) {
Status["Pending"] = "PENDING";
Status["Processing"] = "PROCESSING";
Status["Completed"] = "COMPLETED";
})(Status || (Status = {}));
For simple, skrivebeskyttede sæt af konstanter kan denne genererede kode føles en smule overdreven.
Alternativ 1: Const Assertions
Const assertions er en kraftfuld TypeScript-funktion, der giver dig mulighed for at fortælle kompilatoren, at den skal udlede den mest specifikke type muligt for en værdi. Når de bruges med arrays eller objekter, der er beregnet til at repræsentere et fast sæt værdier, kan de tjene som et letvægtsalternativ til enums.
Const Assertions med Arrays
Du kan oprette et array af strengliteraler og derefter bruge en const assertion til at gøre dets type uforanderlig og dets elementer til literal typer.
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");
Lad os nedbryde, hvad der sker her:
as const: Denne assertion fortæller TypeScript, at den skal behandle arrayet som skrivebeskyttet og udlede de mest specifikke literal typer for dets elementer. Så i stedet for `string[]` bliver typen `readonly ["PENDING", "PROCESSING", "COMPLETED"]`.typeof statusArray[number]: Dette er en mapped type. Den itererer over alle indekser istatusArrayog udtrækker deres literal typer.numberindeks-signaturen siger i bund og grund "giv mig typen af ethvert element i dette array." Resultatet er en union type:"PENDING" | "PROCESSING" | "COMPLETED".
Denne tilgang giver typesikkerhed, der ligner streng enums, men genererer minimal JavaScript. Selve statusArray forbliver et array af strenge i JavaScript.
Const Assertions med Objekter
Const assertions er endnu mere kraftfulde, når de anvendes på objekter. Du kan definere et objekt, hvor nøgler repræsenterer dine navngivne konstanter, og værdierne er streng- eller numeriske literaler.
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
I dette objekteksempel:
as const: Denne assertion gør hele objektet skrivebeskyttet. Endnu vigtigere er det, at den udleder literal typer for alle egenskabsværdier (f.eks."ADMIN"i stedet forstring) og gør egenskaberne selv skrivebeskyttede.keyof typeof userRoles: Dette udtryk resulterer i en union af nøglerne iuserRolesobjektet, som er"Admin" | "Editor" | "Viewer".typeof userRoles[keyof typeof userRoles]: Dette er en opslagstype. Den tager unionen af nøgler og bruger den til at slå de tilsvarende værdier op iuserRolestypen. Dette resulterer i unionen af værdierne:"ADMIN" | "EDITOR" | "VIEWER", som er vores ønskede type for roller.
JavaScript-outputtet for userRoles vil være et almindeligt JavaScript-objekt:
var userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
};
Dette er betydeligt lettere end en typisk enum.
HvornĂĄr skal man bruge Const Assertions
- Skrivebeskyttede konstanter: Når du har brug for et fast sæt af streng- eller numeriske literaler, der ikke skal ændres under kørsel.
- Minimal JavaScript-output: Hvis du er bekymret for pakkestørrelse og ønsker den mest performante kørselrepræsentation for dine konstanter.
- Objektlignende struktur: Når du foretrækker læsbarheden af nøgle-værdi-par, lignende hvordan du ville strukturere data eller konfiguration.
- Strengbaserede sæt: Særligt nyttigt til at repræsentere tilstande, typer eller kategorier, der bedst identificeres ved beskrivende strenge.
Alternativ 2: Union Typer
Union typer giver dig mulighed for at deklarere, at en variabel kan holde en værdi af en af flere typer. Når de kombineres med literal typer (streng-, tal-, boolean-literaler), udgør de en kraftfuld måde at definere et sæt af tilladte værdier på uden at skulle have en eksplicit konstant deklaration for sættet selv.
Union Typer med Streng Literaler
Du kan direkte definere en union af streng literaler.
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
Dette er den mest direkte og ofte den mest præcise måde at definere et sæt af tilladte strengværdier på.
Union Typer med Numeriske Literaler
Tilsvarende kan du bruge numeriske literaler.
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);
HvornĂĄr skal man bruge Union Typer
- Simple, direkte sæt: Når sættet af tilladte værdier er lille, klart og ikke kræver beskrivende nøgler ud over værdierne selv.
- Implicit konstanter: Når du ikke behøver at henvise til en navngivet konstant for selve sættet, men snarere direkte bruger de literal værdier.
- Maksimal præcision: Til ligefremme scenarier, hvor det at definere et dedikeret objekt eller array føles som overkill.
- Funktionsparametre/returtyper: Fremragende til at definere det nøjagtige sæt af acceptable streng- eller nummerinput/output for funktioner.
Sammenligning af Enums, Const Assertions og Union Typer
Lad os opsummere de vigtigste forskelle og brugsscenarier:
Kørselstid Opførsel
- Enums: Genererer JavaScript-objekter, potentielt med omvendte mappinger.
- Const Assertions (Arrays/Objekter): Genererer almindelige JavaScript-arrays eller -objekter. Typeinformationen slettes under kørsel, men datastrukturen bevares.
- Union Typer (med literaler): Ingen kørselstidsrepræsentation for unionen selv. Værdierne er blot literaler. Typekontrol sker udelukkende under kompilering.
Læsbarhed og Udtryksfuldhed
- Enums: Høj læsbarhed, især med beskrivende navne. Kan være mere omstændelig.
- Const Assertions (Objekter): God læsbarhed gennem nøgle-værdi-par, der efterligner konfigurationer eller indstillinger.
- Const Assertions (Arrays): Mindre læsbare til at repræsentere navngivne konstanter, mere til blot en ordnet liste af værdier.
- Union Typer: Meget præcise. Læsbarhed afhænger af klarheden af de literal værdier selv.
Typesikkerhed
- Alle tre tilgange tilbyder stærk typesikkerhed. De sikrer, at kun gyldige, foruddefinerede værdier kan tildeles variabler eller sendes til funktioner.
Pakkestørrelse
- Enums: Generelt de største på grund af genererede JavaScript-objekter.
- Const Assertions: Mindre end enums, da de producerer almindelige datastrukturer.
- Union Typer: De mindste, da de ikke genererer nogen specifik kørselstidsdatastruktur for selve typen, men kun baserer sig på literal værdier.
Brugsscenarie Matrix
Her er en hurtig guide:
| Funktion | TypeScript Enum | Const Assertion (Objekt) | Const Assertion (Array) | Union Type (Literaler) |
|---|---|---|---|---|
| Kørselstid Output | JS Objekt (med omvendt mapping) | Almindeligt JS Objekt | Almindeligt JS Array | Ingen (kun literal værdier) |
| Læsbarhed (Navngivne Konstanter) | Høj | Høj | Medium | Lav (værdier er navne) |
| Pakkestørrelse | Størst | Medium | Medium | Mindst |
| Fleksibilitet | God | God | God | Fremragende (for simple sæt) |
| Almindelig Anvendelse | Tilstande, Statuskoder, Kategorier | Konfiguration, Rolledefinitioner, Feature Flags | Ordnet liste af uforanderlige værdier | Funktionsparametre, simple begrænsede værdier |
Praktiske Eksempler og Bedste Praksis
Eksempel 1: Repræsentation af API Statuskoder
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 ...
}
Anbefaling: For dette scenarie er en union type ofte den mest præcise og effektive. De literal værdier er i sig selv tilstrækkeligt beskrivende. Hvis du havde brug for at associere yderligere metadata med hver status (f.eks. en brugervenlig meddelelse), ville et const assertion-objekt være et bedre valg.
Eksempel 2: Definition af Brugerroller
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 ...
}
Anbefaling: Et const assertion objekt rammer en god balance her. Det giver klare nøgle-værdi-par (f.eks. userRolesObject.Admin), hvilket kan forbedre læsbarheden, når der refereres til roller, samtidig med at det er performant. En union type er også en meget stærk kandidat, hvis direkte strengliteraler er tilstrækkelige.
Eksempel 3: Repræsentation af Konfigurationsmuligheder
Forestil dig et konfigurationsobjekt for en global applikation, der kan have forskellige temaer.
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 ...
}
Anbefaling: For konfigurationsindstillinger som temaer er const assertion-objektet ofte ideelt. Det definerer klart de tilgængelige muligheder og deres tilsvarende strengværdier. Nøglerne (Light, Dark, System) er beskrivende og mapper direkte til værdierne, hvilket gør konfigurationskoden meget forståelig.
Valg af det Rette Værktøj til Opgaven
Beslutningen mellem TypeScript enums, const assertions og union typer er ikke altid sort-hvid. Det handler ofte om en afvejning mellem kørselstid ydelse, pakkestørrelse og kodens læsbarhed/udtryksfuldhed.
- Vælg Union Typer når du har brug for et simpelt, begrænset sæt af streng- eller numeriske literaler, og maksimal præcision ønskes. De er fremragende til funktionssignaturer og grundlæggende værdi-begrænsninger.
- Vælg Const Assertions (med Objekter) når du ønsker en mere struktureret, læsbar måde at definere navngivne konstanter på, lignende en enum, men med betydeligt mindre runtime-overhead. Dette er fantastisk til konfiguration, roller eller ethvert sæt, hvor nøglerne tilføjer betydelig mening.
- Vælg Const Assertions (med Arrays) når du simpelthen har brug for en uforanderlig, ordnet liste af værdier, og direkte adgang via indeks er vigtigere end navngivne nøgler.
- Overvej TypeScript Enums når du har brug for deres specifikke funktioner, såsom omvendt mapping (selvom dette er mindre almindeligt i moderne udvikling), eller hvis dit team har en stærk præference, og ydelsespåvirkningen er ubetydelig for dit projekt.
I mange moderne TypeScript-projekter vil du finde en tendens mod const assertions og union typer frem for traditionelle enums, især for strengbaserede konstanter, på grund af deres bedre ydelsesegenskaber og ofte simplere JavaScript-output.
Globale Overvejelser
Når man udvikler applikationer til et globalt publikum, er konsistente og forudsigelige konstantdefinitioner afgørende. De valg, vi har diskuteret (enums, const assertions, union typer), bidrager alle til denne konsistens ved at håndhæve typesikkerhed på tværs af forskellige miljøer og udviklerlokaler.
- Konsistens: Uanset den valgte metode er nøglen konsistens inden for dit projekt. Hvis du beslutter dig for at bruge const assertion-objekter til roller, så hold fast i dette mønster i hele kodebasen.
- Internationalisering (i18n): Når du definerer etiketter eller meddelelser, der skal internationaliseres, skal du bruge disse typesikre strukturer for at sikre, at kun gyldige nøgler eller identifikatorer bruges. De faktiske oversatte strenge vil blive administreret separat via i18n-biblioteker. For eksempel, hvis du har et `status`-felt, der kan være "PENDING", "PROCESSING", "COMPLETED", ville dit i18n-bibliotek mappe disse interne identifikatorer til lokaliseret visningstekst.
- Tidszoner & Valutaer: Selvom det ikke er direkte relateret til enums, skal du huske, at når du håndterer værdier som datoer, tider eller valutaer, kan TypeScript's typesystem hjælpe med at håndhæve korrekt brug, men eksterne biblioteker er normalt nødvendige for nøjagtig global håndtering. For eksempel kunne en `Currency` union type defineres som `"USD" | "EUR" | "GBP"`, men den faktiske konverteringslogik kræver specialiserede værktøjer.
Konklusion
TypeScript tilbyder et rigt sæt værktøjer til styring af konstanter. Mens enums har tjent os godt, tilbyder const assertions og union typer overbevisende, ofte mere performante, alternativer. Ved at forstå deres forskelle og vælge den rette tilgang baseret på dine specifikke behov – hvad enten det er ydelse, læsbarhed eller præcision – kan du skrive mere robust, vedligeholdelsesvenlig og effektiv TypeScript-kode, der skalerer globalt.
At omfavne disse alternativer kan føre til mindre pakkestørrelser, hurtigere applikationer og en mere forudsigelig udvikleroplevelse for dit internationale team.