Jelajahi alternatif enum TypeScript yang kuat: const assertions dan union types. Pelajari kapan harus menggunakan masing-masing untuk kode yang kokoh dan mudah dipelihara.
Melampaui Enum: Perbandingan Const Assertions dan Union Types pada TypeScript
Dalam dunia JavaScript yang diketik secara statis dengan TypeScript, enum telah lama menjadi pilihan utama untuk merepresentasikan sekumpulan konstanta bernama yang tetap. Enum menawarkan cara yang jelas dan mudah dibaca untuk mendefinisikan koleksi nilai yang saling terkait. Namun, seiring dengan pertumbuhan dan evolusi proyek, pengembang sering mencari alternatif yang lebih fleksibel dan terkadang lebih berperforma. Dua pesaing kuat yang sering muncul adalah const assertions dan union types. Artikel ini akan menggali nuansa penggunaan alternatif ini dibandingkan enum tradisional, memberikan contoh praktis, dan memandu Anda kapan harus memilih yang mana.
Memahami Enum TypeScript Tradisional
Sebelum kita menjelajahi alternatifnya, penting untuk memiliki pemahaman yang kuat tentang cara kerja enum standar TypeScript. Enum memungkinkan Anda untuk mendefinisikan sekumpulan konstanta numerik atau string bernama. Mereka bisa berupa numerik (default) atau berbasis string.
Enum Numerik
Secara default, anggota enum diberi nilai numerik mulai dari 0.
enum DirectionNumeric {
Up,
Down,
Left,
Right
}
let myDirection: DirectionNumeric = DirectionNumeric.Up;
console.log(myDirection); // Output: 0
Anda juga dapat secara eksplisit menetapkan nilai numerik.
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500
}
let responseStatus: StatusCode = StatusCode.Success;
console.log(responseStatus); // Output: 200
Enum String
Enum string sering kali lebih disukai karena pengalaman debugging yang lebih baik, karena nama anggota dipertahankan dalam JavaScript yang dikompilasi.
enum ColorString {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
let favoriteColor: ColorString = ColorString.Blue;
console.log(favoriteColor); // Output: "BLUE"
Beban Tambahan (Overhead) dari Enum
Meskipun enum nyaman digunakan, mereka datang dengan sedikit beban tambahan. Saat dikompilasi ke JavaScript, enum TypeScript diubah menjadi objek yang sering kali memiliki pemetaan terbalik (misalnya, memetakan nilai numerik kembali ke nama enum). Ini bisa berguna tetapi juga berkontribusi pada ukuran bundle dan mungkin tidak selalu diperlukan.
Perhatikan contoh enum string sederhana ini:
enum Status {
Pending = "PENDING",
Processing = "PROCESSING",
Completed = "COMPLETED"
}
Di JavaScript, ini mungkin akan menjadi seperti ini:
var Status;
(function (Status) {
Status["Pending"] = "PENDING";
Status["Processing"] = "PROCESSING";
Status["Completed"] = "COMPLETED";
})(Status || (Status = {}));
Untuk kumpulan konstanta sederhana yang hanya-baca, kode yang dihasilkan ini bisa terasa sedikit berlebihan.
Alternatif 1: Const Assertions
Const assertions adalah fitur TypeScript yang kuat yang memungkinkan Anda memberitahu kompiler untuk menyimpulkan tipe yang paling spesifik untuk suatu nilai. Ketika digunakan dengan array atau objek yang dimaksudkan untuk mewakili serangkaian nilai tetap, mereka dapat berfungsi sebagai alternatif ringan untuk enum.
Const Assertions dengan Array
Anda dapat membuat sebuah array literal string dan kemudian menggunakan const assertion untuk membuat tipenya tidak dapat diubah (immutable) dan elemen-elemennya menjadi tipe literal.
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");
Mari kita uraikan apa yang terjadi di sini:
as const: Asersi ini memberitahu TypeScript untuk memperlakukan array sebagai hanya-baca (read-only) dan menyimpulkan tipe literal yang paling spesifik untuk elemen-elemennya. Jadi, alih-alih `string[]`, tipenya menjadi `readonly ["PENDING", "PROCESSING", "COMPLETED"]`.typeof statusArray[number]: Ini adalah tipe terpetakan (mapped type). Ini mengiterasi semua indeks daristatusArraydan mengekstrak tipe literalnya. Tanda tangan indeksnumberpada dasarnya mengatakan "berikan saya tipe dari elemen apa pun dalam array ini." Hasilnya adalah union type:"PENDING" | "PROCESSING" | "COMPLETED".
Pendekatan ini memberikan keamanan tipe yang mirip dengan enum string tetapi menghasilkan JavaScript yang minimal. statusArray itu sendiri tetap menjadi array string di JavaScript.
Const Assertions dengan Objek
Const assertions bahkan lebih kuat ketika diterapkan pada objek. Anda dapat mendefinisikan sebuah objek di mana kunci mewakili konstanta bernama Anda dan nilainya adalah literal string atau angka.
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
Dalam contoh objek ini:
as const: Asersi ini membuat seluruh objek menjadi hanya-baca. Lebih penting lagi, ia menyimpulkan tipe literal untuk semua nilai properti (misalnya,"ADMIN"alih-alihstring) dan membuat properti itu sendiri menjadi readonly.keyof typeof userRoles: Ekspresi ini menghasilkan union dari kunci-kunci objekuserRoles, yaitu"Admin" | "Editor" | "Viewer".typeof userRoles[keyof typeof userRoles]: Ini adalah tipe pencarian (lookup type). Ia mengambil union dari kunci dan menggunakannya untuk mencari nilai yang sesuai dalam tipeuserRoles. Ini menghasilkan union dari nilai-nilainya:"ADMIN" | "EDITOR" | "VIEWER", yang merupakan tipe yang kita inginkan untuk peran.
Output JavaScript untuk userRoles akan menjadi objek JavaScript biasa:
var userRoles = {
Admin: "ADMIN",
Editor: "EDITOR",
Viewer: "VIEWER"
};
Ini jauh lebih ringan daripada enum pada umumnya.
Kapan Menggunakan Const Assertions
- Konstanta hanya-baca: Ketika Anda membutuhkan sekumpulan literal string atau angka yang tetap dan tidak boleh berubah saat runtime.
- Output JavaScript minimal: Jika Anda khawatir tentang ukuran bundle dan menginginkan representasi runtime yang paling berperforma untuk konstanta Anda.
- Struktur seperti objek: Ketika Anda lebih menyukai keterbacaan pasangan kunci-nilai, mirip dengan cara Anda menyusun data atau konfigurasi.
- Kumpulan berbasis string: Sangat berguna untuk merepresentasikan status, tipe, atau kategori yang paling baik diidentifikasi oleh string deskriptif.
Alternatif 2: Union Types
Union types memungkinkan Anda untuk menyatakan bahwa sebuah variabel dapat menampung nilai dari salah satu dari beberapa tipe. Ketika digabungkan dengan tipe literal (literal string, angka, boolean), mereka membentuk cara yang kuat untuk mendefinisikan sekumpulan nilai yang diizinkan tanpa memerlukan deklarasi konstanta eksplisit untuk himpunan itu sendiri.
Union Types dengan Literal String
Anda dapat secara langsung mendefinisikan sebuah union dari literal string.
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
Ini adalah cara yang paling langsung dan seringkali paling ringkas untuk mendefinisikan sekumpulan nilai string yang diizinkan.
Union Types dengan Literal Numerik
Serupa dengan itu, Anda dapat menggunakan literal numerik.
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);
Kapan Menggunakan Union Types
- Kumpulan sederhana dan langsung: Ketika kumpulan nilai yang diizinkan kecil, jelas, dan tidak memerlukan kunci deskriptif selain dari nilai itu sendiri.
- Konstanta implisit: Ketika Anda tidak perlu merujuk ke konstanta bernama untuk himpunan itu sendiri, tetapi langsung menggunakan nilai literal.
- Keringkasan maksimal: Untuk skenario sederhana di mana mendefinisikan objek atau array khusus terasa berlebihan.
- Parameter/tipe kembalian fungsi: Sangat baik untuk mendefinisikan kumpulan input/output string atau angka yang dapat diterima secara tepat untuk fungsi.
Membandingkan Enum, Const Assertions, dan Union Types
Mari kita rangkum perbedaan utama dan kasus penggunaannya:
Perilaku Runtime
- Enum: Menghasilkan objek JavaScript, berpotensi dengan pemetaan terbalik.
- Const Assertions (Array/Objek): Menghasilkan array atau objek JavaScript biasa. Informasi tipe dihapus saat runtime, tetapi struktur data tetap ada.
- Union Types (dengan literal): Tidak ada representasi runtime untuk union itu sendiri. Nilainya hanyalah literal. Pengecekan tipe terjadi murni pada waktu kompilasi.
Keterbacaan dan Ekspresivitas
- Enum: Keterbacaan tinggi, terutama dengan nama deskriptif. Bisa lebih bertele-tele.
- Const Assertions (Objek): Keterbacaan baik melalui pasangan kunci-nilai, meniru konfigurasi atau pengaturan.
- Const Assertions (Array): Kurang mudah dibaca untuk merepresentasikan konstanta bernama, lebih cocok untuk daftar nilai yang terurut.
- Union Types: Sangat ringkas. Keterbacaan tergantung pada kejelasan nilai literal itu sendiri.
Keamanan Tipe
- Semua ketiga pendekatan menawarkan keamanan tipe yang kuat. Mereka memastikan bahwa hanya nilai yang valid dan telah ditentukan sebelumnya yang dapat ditetapkan ke variabel atau dilewatkan ke fungsi.
Ukuran Bundle
- Enum: Umumnya yang terbesar karena objek JavaScript yang dihasilkan.
- Const Assertions: Lebih kecil dari enum, karena mereka menghasilkan struktur data biasa.
- Union Types: Yang terkecil, karena mereka tidak menghasilkan struktur data runtime spesifik untuk tipe itu sendiri, hanya mengandalkan nilai literal.
Matriks Kasus Penggunaan
Berikut adalah panduan cepat:
| Fitur | Enum TypeScript | Const Assertion (Objek) | Const Assertion (Array) | Union Type (Literal) |
|---|---|---|---|---|
| Output Runtime | Objek JS (dengan pemetaan terbalik) | Objek JS Biasa | Array JS Biasa | Tidak ada (hanya nilai literal) |
| Keterbacaan (Konstanta Bernama) | Tinggi | Tinggi | Sedang | Rendah (nilai adalah nama) |
| Ukuran Bundle | Terbesar | Sedang | Sedang | Terkecil |
| Fleksibilitas | Baik | Baik | Baik | Sangat Baik (untuk set sederhana) |
| Penggunaan Umum | State, Kode Status, Kategori | Konfigurasi, Definisi Peran, Feature Flags | Daftar nilai immutable yang terurut | Parameter fungsi, nilai terbatas sederhana |
Contoh Praktis dan Praktik Terbaik
Contoh 1: Merepresentasikan Kode Status API
Enum:
enum ApiStatus {
Success = "SUCCESS",
Error = "ERROR",
Pending = "PENDING"
}
function handleApiResponse(status: ApiStatus) {
// ... logic ...
}
Const Assertion (Objek):
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 ...
}
Rekomendasi: Untuk skenario ini, union type seringkali menjadi yang paling ringkas dan efisien. Nilai literal itu sendiri sudah cukup deskriptif. Jika Anda perlu mengaitkan metadata tambahan dengan setiap status (misalnya, pesan yang ramah pengguna), objek const assertion akan menjadi pilihan yang lebih baik.
Contoh 2: Mendefinisikan Peran Pengguna
Enum:
enum UserRoleEnum {
Admin = "ADMIN",
Moderator = "MODERATOR",
User = "USER"
}
function getUserPermissions(role: UserRoleEnum) {
// ... logic ...
}
Const Assertion (Objek):
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 ...
}
Rekomendasi: Objek const assertion mencapai keseimbangan yang baik di sini. Ini menyediakan pasangan kunci-nilai yang jelas (misalnya, userRolesObject.Admin) yang dapat meningkatkan keterbacaan saat merujuk peran, sambil tetap berperforma. Union type juga merupakan pesaing yang sangat kuat jika literal string langsung sudah cukup.
Contoh 3: Merepresentasikan Opsi Konfigurasi
Bayangkan sebuah objek konfigurasi untuk aplikasi global yang mungkin memiliki tema yang berbeda.
Enum:
enum Theme {
Light = "light",
Dark = "dark",
System = "system"
}
interface AppConfig {
theme: Theme;
// ... other config options ...
}
Const Assertion (Objek):
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 ...
}
Rekomendasi: Untuk pengaturan konfigurasi seperti tema, objek const assertion seringkali ideal. Ini dengan jelas mendefinisikan opsi yang tersedia dan nilai string yang sesuai. Kunci (Light, Dark, System) bersifat deskriptif dan memetakan langsung ke nilai, membuat kode konfigurasi sangat mudah dipahami.
Memilih Alat yang Tepat untuk Pekerjaan
Keputusan antara enum TypeScript, const assertions, dan union types tidak selalu hitam dan putih. Seringkali ini adalah pertukaran antara kinerja runtime, ukuran bundle, dan keterbacaan/ekspresivitas kode.
- Pilih Union Types ketika Anda membutuhkan set literal string atau angka yang sederhana dan terbatas, dan menginginkan keringkasan maksimal. Mereka sangat baik untuk signature fungsi dan batasan nilai dasar.
- Pilih Const Assertions (dengan Objek) ketika Anda menginginkan cara yang lebih terstruktur dan mudah dibaca untuk mendefinisikan konstanta bernama, mirip dengan enum, tetapi dengan overhead runtime yang jauh lebih sedikit. Ini bagus untuk konfigurasi, peran, atau set apa pun di mana kunci menambahkan makna yang signifikan.
- Pilih Const Assertions (dengan Array) ketika Anda hanya membutuhkan daftar nilai yang terurut dan tidak dapat diubah, dan akses langsung melalui indeks lebih penting daripada kunci bernama.
- Pertimbangkan Enum TypeScript ketika Anda membutuhkan fitur spesifik mereka, seperti pemetaan terbalik (meskipun ini kurang umum dalam pengembangan modern) atau jika tim Anda memiliki preferensi yang kuat dan dampak kinerjanya dapat diabaikan untuk proyek Anda.
Dalam banyak proyek TypeScript modern, Anda akan menemukan kecenderungan ke arah const assertions dan union types daripada enum tradisional, terutama untuk konstanta berbasis string, karena karakteristik kinerja yang lebih baik dan output JavaScript yang seringkali lebih sederhana.
Pertimbangan Global
Saat mengembangkan aplikasi untuk audiens global, definisi konstanta yang konsisten dan dapat diprediksi sangat penting. Pilihan yang telah kita diskusikan (enum, const assertions, union types) semuanya berkontribusi pada konsistensi ini dengan menegakkan keamanan tipe di berbagai lingkungan dan lokal pengembang.
- Konsistensi: Terlepas dari metode yang dipilih, kuncinya adalah konsistensi dalam proyek Anda. Jika Anda memutuskan untuk menggunakan objek const assertion untuk peran, tetaplah dengan pola itu di seluruh basis kode.
- Internasionalisasi (i18n): Saat mendefinisikan label atau pesan yang akan diinternasionalkan, gunakan struktur yang aman tipe ini untuk memastikan bahwa hanya kunci atau pengidentifikasi yang valid yang digunakan. String terjemahan yang sebenarnya akan dikelola secara terpisah melalui pustaka i18n. Misalnya, jika Anda memiliki bidang `status` yang bisa berupa "PENDING", "PROCESSING", "COMPLETED", pustaka i18n Anda akan memetakan pengidentifikasi internal ini ke teks tampilan yang dilokalkan.
- Zona Waktu & Mata Uang: Meskipun tidak terkait langsung dengan enum, ingatlah bahwa saat berurusan dengan nilai seperti tanggal, waktu, atau mata uang, sistem tipe TypeScript dapat membantu menegakkan penggunaan yang benar, tetapi pustaka eksternal biasanya diperlukan untuk penanganan global yang akurat. Misalnya, tipe union `Currency` dapat didefinisikan sebagai `"USD" | "EUR" | "GBP"`, tetapi logika konversi yang sebenarnya memerlukan alat khusus.
Kesimpulan
TypeScript menyediakan seperangkat alat yang kaya untuk mengelola konstanta. Meskipun enum telah melayani kita dengan baik, const assertions dan union types menawarkan alternatif yang menarik, dan seringkali lebih berperforma. Dengan memahami perbedaannya dan memilih pendekatan yang tepat berdasarkan kebutuhan spesifik Anda—baik itu kinerja, keterbacaan, atau keringkasan—Anda dapat menulis kode TypeScript yang lebih kokoh, mudah dipelihara, dan efisien yang dapat diskalakan secara global.
Menerima alternatif ini dapat menghasilkan ukuran bundle yang lebih kecil, aplikasi yang lebih cepat, dan pengalaman pengembang yang lebih dapat diprediksi untuk tim internasional Anda.