Jelajahi alternatif enum TypeScript yang andal seperti const assertion dan union type. Pahami manfaat, kekurangan, dan aplikasi praktisnya untuk kode yang lebih bersih dan mudah dipelihara dalam konteks pengembangan global.
Alternatif Enum TypeScript: Menavigasi Const Assertion dan Union Type untuk Kode yang Tangguh
TypeScript, superset JavaScript yang andal, membawa pengetikan statis ke dunia pengembangan web yang dinamis. Di antara banyak fiturnya, kata kunci enum telah lama menjadi pilihan utama untuk mendefinisikan sekumpulan konstanta bernama. Enum menyediakan cara yang jelas untuk merepresentasikan koleksi nilai terkait yang tetap, meningkatkan keterbacaan dan keamanan tipe (type safety).
Namun, seiring matangnya ekosistem TypeScript dan proyek-proyek yang berkembang dalam kompleksitas dan skala, para pengembang di seluruh dunia semakin mempertanyakan kegunaan tradisional enum. Meskipun sederhana untuk kasus-kasus simpel, enum memperkenalkan perilaku dan karakteristik runtime tertentu yang terkadang dapat menyebabkan masalah tak terduga, memengaruhi ukuran bundle, atau mempersulit optimisasi tree-shaking. Hal ini telah mendorong eksplorasi alternatif secara luas.
Panduan komprehensif ini akan membahas secara mendalam dua alternatif utama yang sangat efektif untuk enum TypeScript: Union Type dengan Literal String/Numerik dan Const Assertion (as const). Kami akan menjelajahi mekanisme, aplikasi praktis, manfaat, dan trade-off dari keduanya, memberikan Anda pengetahuan untuk membuat keputusan desain yang tepat untuk proyek Anda, terlepas dari ukurannya atau tim global yang mengerjakannya. Tujuan kami adalah memberdayakan Anda untuk menulis kode TypeScript yang lebih tangguh, mudah dipelihara, dan efisien.
Enum TypeScript: Tinjauan Singkat
Sebelum kita membahas alternatif, mari kita tinjau sejenak enum TypeScript tradisional. Enum memungkinkan pengembang untuk mendefinisikan sekumpulan konstanta bernama, membuat kode lebih mudah dibaca dan mencegah "magic strings" atau "magic numbers" tersebar di seluruh aplikasi. Mereka hadir dalam dua bentuk utama: enum numerik dan string.
Enum Numerik
Secara default, enum TypeScript bersifat numerik. Anggota pertama diinisialisasi dengan 0, dan setiap anggota berikutnya akan bertambah secara otomatis.
enum Direction {
Up,
Down,
Left,
Right,
}
let currentDirection: Direction = Direction.Up;
console.log(currentDirection); // Menghasilkan: 0
console.log(Direction.Left); // Menghasilkan: 2
Anda juga dapat menginisialisasi anggota enum numerik secara manual:
enum StatusCode {
Success = 200,
NotFound = 404,
ServerError = 500,
}
let status: StatusCode = StatusCode.NotFound;
console.log(status); // Menghasilkan: 404
Karakteristik unik dari enum numerik adalah pemetaan terbalik (reverse mapping). Saat runtime, enum numerik dikompilasi menjadi objek JavaScript yang memetakan nama ke nilai dan nilai kembali ke nama.
enum UserRole {
Admin = 1,
Editor,
Viewer,
}
console.log(UserRole[1]); // Menghasilkan: "Admin"
console.log(UserRole.Editor); // Menghasilkan: 2
console.log(UserRole[2]); // Menghasilkan: "Editor"
/*
Dikompilasi menjadi JavaScript:
var UserRole;
(function (UserRole) {
UserRole[UserRole["Admin"] = 1] = "Admin";
UserRole[UserRole["Editor"] = 2] = "Editor";
UserRole[UserRole["Viewer"] = 3] = "Viewer";
})(UserRole || (UserRole = {}));
*/
Enum String
Enum string sering kali lebih disukai karena keterbacaannya saat runtime, karena tidak bergantung pada angka yang bertambah otomatis. Setiap anggota harus diinisialisasi dengan literal string.
enum UserPermission {
Read = "READ_PERMISSION",
Write = "WRITE_PERMISSION",
Delete = "DELETE_PERMISSION",
}
let permission: UserPermission = UserPermission.Write;
console.log(permission); // Menghasilkan: "WRITE_PERMISSION"
Enum string tidak mendapatkan pemetaan terbalik, yang umumnya merupakan hal yang baik untuk menghindari perilaku runtime yang tidak terduga dan mengurangi output JavaScript yang dihasilkan.
Pertimbangan Utama dan Potensi Kelemahan Enum
Meskipun enum menawarkan kemudahan, mereka datang dengan karakteristik tertentu yang memerlukan pertimbangan cermat:
- Objek Runtime: Baik enum numerik maupun string menghasilkan objek JavaScript saat runtime. Ini berarti mereka berkontribusi pada ukuran bundle aplikasi Anda, bahkan jika Anda hanya menggunakannya untuk pemeriksaan tipe. Untuk proyek kecil, ini mungkin dapat diabaikan, tetapi dalam aplikasi skala besar dengan banyak enum, hal ini dapat bertambah.
- Kurangnya Tree-Shaking: Karena enum adalah objek runtime, mereka sering kali tidak dapat di-tree-shake secara efektif oleh bundler modern seperti Webpack atau Rollup. Jika Anda mendefinisikan sebuah enum tetapi hanya menggunakan satu atau dua anggotanya, seluruh objek enum mungkin masih akan disertakan dalam bundle akhir Anda. Ini dapat menyebabkan ukuran file yang lebih besar dari yang diperlukan.
- Pemetaan Terbalik (Enum Numerik): Fitur pemetaan terbalik pada enum numerik, meskipun terkadang berguna, juga bisa menjadi sumber kebingungan dan perilaku tak terduga. Ini menambahkan kode ekstra ke output JavaScript dan mungkin tidak selalu merupakan fungsionalitas yang diinginkan. Misalnya, serialisasi enum numerik terkadang dapat menyebabkan hanya angkanya yang disimpan, yang mungkin tidak deskriptif seperti string.
- Overhead Transpilasi: Kompilasi enum menjadi objek JavaScript menambahkan sedikit overhead pada proses build dibandingkan dengan hanya mendefinisikan variabel konstan.
- Iterasi Terbatas: Melakukan iterasi langsung pada nilai enum bisa jadi tidak trivial, terutama dengan enum numerik karena adanya pemetaan terbalik. Anda sering kali memerlukan fungsi pembantu atau loop spesifik untuk mendapatkan hanya nilai yang diinginkan.
Poin-poin ini menyoroti mengapa banyak tim pengembangan global, terutama yang berfokus pada performa dan ukuran bundle, mencari alternatif yang memberikan keamanan tipe serupa tanpa jejak runtime atau kompleksitas lainnya.
Alternatif 1: Union Type dengan Literal
Salah satu alternatif enum yang paling sederhana dan kuat di TypeScript adalah penggunaan Union Type dengan Literal String atau Numerik. Pendekatan ini memanfaatkan sistem tipe TypeScript yang tangguh untuk mendefinisikan sekumpulan nilai spesifik yang diizinkan pada waktu kompilasi (compile-time), tanpa memperkenalkan konstruksi baru saat runtime.
Apa itu Union Type?
Union type mendeskripsikan sebuah nilai yang dapat berupa salah satu dari beberapa tipe. Sebagai contoh, string | number berarti sebuah variabel dapat berisi string atau angka. Ketika digabungkan dengan tipe literal (misalnya, "success", 404), Anda dapat mendefinisikan sebuah tipe yang hanya dapat berisi sekumpulan nilai yang telah ditentukan sebelumnya.
Contoh Praktis: Mendefinisikan Status dengan Union Type
Mari kita pertimbangkan skenario umum: mendefinisikan sekumpulan status yang mungkin untuk sebuah pekerjaan pemrosesan data atau akun pengguna. Dengan union type, ini terlihat bersih dan ringkas:
type JobStatus = "PENDING" | "IN_PROGRESS" | "COMPLETED" | "FAILED";
function processJob(status: JobStatus): void {
if (status === "COMPLETED") {
console.log("Pekerjaan selesai dengan sukses.");
} else if (status === "FAILED") {
console.log("Pekerjaan mengalami error.");
} else {
console.log(`Pekerjaan saat ini ${status}.`);
}
}
let currentJobStatus: JobStatus = "IN_PROGRESS";
processJob(currentJobStatus);
// Ini akan menghasilkan error saat kompilasi:
// let invalidStatus: JobStatus = "CANCELLED"; // Error: Type '"CANCELLED"' is not assignable to type 'JobStatus'.
Untuk nilai numerik, polanya identik:
type HttpCode = 200 | 400 | 404 | 500;
function handleResponse(code: HttpCode): void {
if (code === 200) {
console.log("Operasi berhasil.");
} else if (code === 404) {
console.log("Sumber daya tidak ditemukan.");
}
}
let responseStatus: HttpCode = 200;
handleResponse(responseStatus);
Perhatikan bagaimana kita mendefinisikan alias type di sini. Ini murni konstruksi compile-time. Ketika dikompilasi ke JavaScript, JobStatus akan hilang, dan literal string/angka digunakan secara langsung.
Manfaat Union Type dengan Literal
Pendekatan ini menawarkan beberapa keuntungan yang menarik:
- Murni Compile-Time: Union type sepenuhnya dihapus selama kompilasi. Mereka tidak menghasilkan kode JavaScript apa pun saat runtime, yang mengarah ke ukuran bundle yang lebih kecil dan waktu muat aplikasi yang lebih cepat. Ini adalah keuntungan signifikan untuk aplikasi yang kritis terhadap performa dan yang diterapkan secara global di mana setiap kilobyte berarti.
- Keamanan Tipe yang Sangat Baik: TypeScript secara ketat memeriksa penugasan terhadap tipe literal yang ditentukan, memberikan jaminan kuat bahwa hanya nilai yang valid yang digunakan. Ini mencegah bug umum yang terkait dengan salah ketik atau nilai yang salah.
- Tree-Shaking Optimal: Karena tidak ada objek runtime, union type secara inheren mendukung tree-shaking. Bundler Anda hanya menyertakan literal string atau numerik yang Anda gunakan, bukan seluruh objek.
- Keterbacaan: Untuk sekumpulan nilai yang sederhana, berbeda, dan tetap, definisi tipenya seringkali sangat jelas dan mudah dipahami.
- Kesederhanaan: Tidak ada konstruksi bahasa baru atau artefak kompilasi yang kompleks yang diperkenalkan. Ini hanya memanfaatkan fitur tipe fundamental TypeScript.
- Akses Nilai Langsung: Anda bekerja langsung dengan nilai string atau angka, yang menyederhanakan serialisasi dan deserialisasi, terutama saat berinteraksi dengan API atau basis data yang mengharapkan pengidentifikasi string tertentu.
Kekurangan Union Type dengan Literal
Meskipun kuat, union type juga memiliki beberapa keterbatasan:
- Pengulangan untuk Data Terkait: Jika Anda perlu mengaitkan data atau metadata tambahan dengan setiap anggota "enum" (misalnya, label tampilan, ikon, warna), Anda tidak dapat melakukannya langsung dalam definisi union type. Anda biasanya memerlukan objek pemetaan terpisah.
- Tidak Ada Iterasi Langsung untuk Semua Nilai: Tidak ada cara bawaan untuk mendapatkan array dari semua nilai yang mungkin dari sebuah union type saat runtime. Misalnya, Anda tidak dapat dengan mudah mendapatkan
["PENDING", "IN_PROGRESS", "COMPLETED", "FAILED"]langsung dariJobStatus. Ini sering kali mengharuskan pemeliharaan array nilai terpisah jika Anda perlu menampilkannya di UI (misalnya, menu dropdown). - Kurang Terpusat: Jika kumpulan nilai dibutuhkan baik sebagai tipe maupun sebagai array nilai runtime, Anda mungkin mendapati diri Anda mendefinisikan daftar tersebut dua kali (sekali sebagai tipe, sekali sebagai array runtime), yang dapat menimbulkan potensi desinkronisasi.
Meskipun memiliki kekurangan ini, untuk banyak skenario, union type menyediakan solusi yang bersih, berperforma tinggi, dan aman secara tipe yang selaras dengan praktik pengembangan JavaScript modern.
Alternatif 2: Const Assertion (as const)
Assertion as const, yang diperkenalkan di TypeScript 3.4, adalah alat lain yang sangat kuat yang menawarkan alternatif yang sangat baik untuk enum, terutama ketika Anda membutuhkan objek runtime dan inferensi tipe yang tangguh. Ini memungkinkan TypeScript untuk menyimpulkan tipe sesempit mungkin untuk ekspresi literal.
Apa itu Const Assertion?
Ketika Anda menerapkan as const ke variabel, array, atau literal objek, TypeScript memperlakukan semua properti dalam literal tersebut sebagai readonly dan menyimpulkan tipe literalnya alih-alih tipe yang lebih luas (misalnya, "foo" alih-alih string, 123 alih-alih number). Ini memungkinkan untuk menurunkan union type yang sangat spesifik dari struktur data runtime.
Contoh Praktis: Membuat Objek "Pseudo-Enum" dengan as const
Mari kita kembali ke contoh status pekerjaan kita. Dengan as const, kita dapat mendefinisikan satu sumber kebenaran (single source of truth) untuk status kita, yang berfungsi sebagai objek runtime dan dasar untuk definisi tipe.
const JobStatuses = {
PENDING: "PENDING",
IN_PROGRESS: "IN_PROGRESS",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
} as const;
// JobStatuses.PENDING sekarang disimpulkan sebagai tipe "PENDING" (bukan hanya string)
// JobStatuses disimpulkan sebagai tipe {
// readonly PENDING: "PENDING";
// readonly IN_PROGRESS: "IN_PROGRESS";
// readonly COMPLETED: "COMPLETED";
// readonly FAILED: "FAILED";
// }
Pada titik ini, JobStatuses adalah objek JavaScript saat runtime, sama seperti enum biasa. Namun, inferensi tipenya jauh lebih presisi.
Menggabungkan dengan typeof dan keyof untuk Union Type
Kekuatan sebenarnya muncul ketika kita menggabungkan as const dengan operator typeof dan keyof TypeScript untuk menurunkan union type dari nilai atau kunci objek.
const JobStatuses = {
PENDING: "PENDING",
IN_PROGRESS: "IN_PROGRESS",
COMPLETED: "COMPLETED",
FAILED: "FAILED",
} as const;
// Tipe yang merepresentasikan kunci (mis., "PENDING" | "IN_PROGRESS" | ...)
type JobStatusKeys = keyof typeof JobStatuses;
// Tipe yang merepresentasikan nilai (mis., "PENDING" | "IN_PROGRESS" | ...)
type JobStatusValues = typeof JobStatuses[keyof typeof JobStatuses];
function processJobWithConstAssertion(status: JobStatusValues): void {
if (status === JobStatuses.COMPLETED) {
console.log("Pekerjaan selesai dengan sukses.");
} else if (status === JobStatuses.FAILED) {
console.log("Pekerjaan mengalami error.");
} else {
console.log(`Pekerjaan saat ini ${status}.`);
}
}
let currentJobStatusFromObject: JobStatusValues = JobStatuses.IN_PROGRESS;
processJobWithConstAssertion(currentJobStatusFromObject);
// Ini akan menghasilkan error saat kompilasi:
// let invalidStatusFromObject: JobStatusValues = "CANCELLED"; // Error!
Pola ini memberikan yang terbaik dari kedua dunia: objek runtime untuk iterasi atau akses properti langsung, dan union type compile-time untuk pemeriksaan tipe yang ketat.
Manfaat Const Assertion dengan Union Type Turunan
- Satu Sumber Kebenaran: Anda mendefinisikan konstanta sekali dalam objek JavaScript biasa, dan menurunkan akses runtime serta tipe compile-time darinya. Ini secara signifikan mengurangi duplikasi dan meningkatkan kemudahan pemeliharaan di berbagai tim pengembangan.
- Keamanan Tipe: Mirip dengan union type murni, Anda mendapatkan keamanan tipe yang sangat baik, memastikan hanya nilai yang telah ditentukan yang digunakan.
- Iterabilitas saat Runtime: Karena
JobStatusesadalah objek JavaScript biasa, Anda dapat dengan mudah melakukan iterasi pada kunci atau nilainya menggunakan metode JavaScript standar sepertiObject.keys(),Object.values(), atauObject.entries(). Ini sangat berharga untuk UI dinamis (misalnya, mengisi dropdown) atau logging. - Data Terkait: Pola ini secara alami mendukung pengaitan data tambahan dengan setiap anggota "enum".
- Potensi Tree-Shaking yang Lebih Baik (Dibandingkan Enum): Meskipun
as constmembuat objek runtime, itu adalah objek JavaScript standar. Bundler modern umumnya lebih efektif dalam melakukan tree-shaking pada properti yang tidak terpakai atau bahkan seluruh objek jika tidak direferensikan, dibandingkan dengan output kompilasi enum TypeScript. Namun, jika objeknya besar dan hanya beberapa properti yang digunakan, seluruh objek mungkin masih akan disertakan jika diimpor dengan cara yang mencegah tree-shaking granular. - Fleksibilitas: Anda dapat mendefinisikan nilai yang tidak hanya berupa string atau angka tetapi juga objek yang lebih kompleks jika diperlukan, menjadikan ini pola yang sangat fleksibel.
const FileOperations = {
UPLOAD: {
label: "Unggah File",
icon: "upload-icon.svg",
permission: "can_upload"
},
DOWNLOAD: {
label: "Unduh File",
icon: "download-icon.svg",
permission: "can_download"
},
DELETE: {
label: "Hapus File",
icon: "delete-icon.svg",
permission: "can_delete"
},
} as const;
type FileOperationType = keyof typeof FileOperations; // "UPLOAD" | "DOWNLOAD" | "DELETE"
type FileOperationDetail = typeof FileOperations[keyof typeof FileOperations]; // { label: string; icon: string; permission: string; }
function performOperation(opType: FileOperationType) {
const details = FileOperations[opType];
console.log(`Melakukan: ${details.label} (Izin: ${details.permission})`);
}
performOperation("UPLOAD");
Kekurangan Const Assertion
- Kehadiran Objek Runtime: Tidak seperti union type murni, pendekatan ini masih membuat objek JavaScript saat runtime. Meskipun ini adalah objek standar dan seringkali lebih baik untuk tree-shaking daripada enum, itu tidak sepenuhnya dihapus.
- Definisi Tipe Sedikit Lebih Bertele-tele: Menurunkan union type (
keyof typeof ...atautypeof ...[keyof typeof ...]) memerlukan sedikit lebih banyak sintaks daripada sekadar mendaftar literal untuk union type. - Potensi Penyalahgunaan: Jika tidak digunakan dengan hati-hati, objek
as constyang sangat besar masih dapat berkontribusi secara signifikan terhadap ukuran bundle jika isinya tidak di-tree-shake secara efektif di seluruh batas modul.
Untuk skenario di mana Anda memerlukan pemeriksaan tipe compile-time yang kuat dan koleksi nilai runtime yang dapat diiterasi atau menyediakan data terkait, as const seringkali menjadi pilihan yang lebih disukai di antara para pengembang TypeScript di seluruh dunia.
Membandingkan Alternatif: Kapan Menggunakan yang Mana?
Memilih antara union type dan const assertion sebagian besar bergantung pada kebutuhan spesifik Anda mengenai keberadaan runtime, iterabilitas, dan apakah Anda perlu mengaitkan data tambahan dengan konstanta Anda. Mari kita uraikan faktor-faktor pengambilan keputusan.
Kesederhanaan vs. Ketangguhan
- Union Type: Menawarkan kesederhanaan tertinggi ketika Anda hanya memerlukan sekumpulan nilai string atau numerik yang berbeda dan aman secara tipe pada waktu kompilasi. Mereka adalah opsi yang paling ringan.
- Const Assertion: Menyediakan pola yang lebih tangguh ketika Anda memerlukan keamanan tipe compile-time dan objek runtime yang dapat ditanyakan, diiterasi, atau diperluas dengan metadata tambahan. Pengaturan awalnya sedikit lebih bertele-tele, tetapi terbayar dengan fitur-fiturnya.
Kehadiran Runtime vs. Compile-time
- Union Type: Murni merupakan konstruksi compile-time. Mereka sama sekali tidak menghasilkan kode JavaScript. Ini ideal untuk aplikasi di mana meminimalkan ukuran bundle adalah yang terpenting, dan nilai-nilainya sendiri sudah cukup tanpa perlu mengaksesnya sebagai objek saat runtime.
- Const Assertion: Menghasilkan objek JavaScript biasa saat runtime. Objek ini dapat diakses dan digunakan dalam kode JavaScript Anda. Meskipun menambah ukuran bundle, umumnya lebih efisien daripada enum TypeScript dan kandidat yang lebih baik untuk tree-shaking.
Kebutuhan Iterabilitas
- Union Type: Tidak menawarkan cara langsung untuk melakukan iterasi pada semua nilai yang mungkin saat runtime. Jika Anda perlu mengisi menu dropdown atau menampilkan semua opsi, Anda perlu mendefinisikan array terpisah dari nilai-nilai ini, yang berpotensi menyebabkan duplikasi.
- Const Assertion: Unggul di sini. Karena Anda bekerja dengan objek JavaScript standar, Anda dapat dengan mudah menggunakan
Object.keys(),Object.values(), atauObject.entries()untuk mendapatkan array kunci, nilai, atau pasangan kunci-nilai, masing-masing. Ini membuat mereka sempurna untuk UI dinamis atau skenario apa pun yang memerlukan enumerasi runtime.
const PaymentMethods = {
CREDIT_CARD: "Kartu Kredit",
PAYPAL: "PayPal",
BANK_TRANSFER: "Transfer Bank",
} as const;
type PaymentMethodType = keyof typeof PaymentMethods;
// Dapatkan semua kunci (mis., untuk logika internal)
const methodKeys = Object.keys(PaymentMethods) as PaymentMethodType[];
console.log(methodKeys); // ["CREDIT_CARD", "PAYPAL", "BANK_TRANSFER"]
// Dapatkan semua nilai (mis., untuk ditampilkan di dropdown)
const methodLabels = Object.values(PaymentMethods);
console.log(methodLabels); // ["Kartu Kredit", "PayPal", "Transfer Bank"]
// Dapatkan pasangan kunci-nilai (mis., untuk pemetaan)
const methodEntries = Object.entries(PaymentMethods);
console.log(methodEntries); // [["CREDIT_CARD", "Kartu Kredit"], ...]
Implikasi Tree-Shaking
- Union Type: Secara inheren dapat di-tree-shake karena hanya ada pada waktu kompilasi.
- Const Assertion: Meskipun mereka membuat objek runtime, bundler modern seringkali dapat melakukan tree-shake pada properti yang tidak terpakai dari objek ini lebih efektif daripada dengan objek enum yang dihasilkan TypeScript. Namun, jika seluruh objek diimpor dan direferensikan, kemungkinan besar akan disertakan. Desain modul yang cermat dapat membantu.
Praktik Terbaik dan Pendekatan Hibrida
Ini tidak selalu situasi "pilih salah satu". Seringkali, solusi terbaik melibatkan pendekatan hibrida, terutama dalam aplikasi besar yang diinternasionalisasi:
- Untuk flag atau pengidentifikasi internal yang sederhana, murni, yang tidak pernah perlu diiterasi atau memiliki data terkait, Union Type umumnya merupakan pilihan yang paling berkinerja dan bersih.
- Untuk sekumpulan konstanta yang perlu diiterasi, ditampilkan di UI, atau memiliki metadata terkait yang kaya (seperti label, ikon, atau izin), pola Const Assertion lebih unggul.
- Menggabungkan untuk Keterbacaan dan Lokalisasi: Banyak tim menggunakan
as constuntuk pengidentifikasi internal dan kemudian menurunkan label tampilan yang dilokalkan dari sistem internasionalisasi (i18n) terpisah.
// src/constants/order-status.ts
const OrderStatuses = {
PENDING: "PENDING",
PROCESSING: "PROCESSING",
SHIPPED: "SHIPPED",
DELIVERED: "DELIVERED",
CANCELLED: "CANCELLED",
} as const;
type OrderStatus = typeof OrderStatuses[keyof typeof OrderStatuses];
export { OrderStatuses, type OrderStatus };
// src/i18n/id.json
{
"orderStatus": {
"PENDING": "Menunggu Konfirmasi",
"PROCESSING": "Memproses Pesanan",
"SHIPPED": "Dikirim",
"DELIVERED": "Terkirim",
"CANCELLED": "Dibatalkan"
}
}
// src/components/OrderStatusDisplay.tsx
import { OrderStatuses, type OrderStatus } from "../constants/order-status";
import { useTranslation } from "react-i18next"; // Contoh library i18n
interface OrderStatusDisplayProps {
status: OrderStatus;
}
function OrderStatusDisplay({ status }: OrderStatusDisplayProps) {
const { t } = useTranslation();
const displayLabel = t(`orderStatus.${status}`);
return <span>Status: {displayLabel}</span>;
}
// Penggunaan:
// <OrderStatusDisplay status={OrderStatuses.DELIVERED} />
Pendekatan hibrida ini memanfaatkan keamanan tipe dan iterabilitas runtime dari as const sambil menjaga string tampilan yang dilokalkan tetap terpisah dan dapat dikelola, sebuah pertimbangan penting untuk aplikasi global.
Pola dan Pertimbangan Lanjutan
Di luar penggunaan dasar, baik union type maupun const assertion dapat diintegrasikan ke dalam pola yang lebih canggih untuk lebih meningkatkan kualitas dan kemudahan pemeliharaan kode.
Menggunakan Type Guard dengan Union Type
Saat bekerja dengan union type, terutama ketika union mencakup tipe yang beragam (bukan hanya literal), type guard menjadi penting untuk mempersempit tipe. Dengan union type literal, discriminated union menawarkan kekuatan yang luar biasa.
type SuccessEvent = { type: "SUCCESS"; data: any; };
type ErrorEvent = { type: "ERROR"; message: string; code: number; };
type SystemEvent = SuccessEvent | ErrorEvent;
function handleSystemEvent(event: SystemEvent) {
if (event.type === "SUCCESS") {
console.log("Data diterima:", event.data);
// event sekarang dipersempit menjadi SuccessEvent
} else {
console.log("Terjadi error:", event.message, "Kode:", event.code);
// event sekarang dipersempit menjadi ErrorEvent
}
}
handleSystemEvent({ type: "SUCCESS", data: { user: "Alice" } });
handleSystemEvent({ type: "ERROR", message: "Kegagalan jaringan", code: 503 });
Pola ini, yang sering disebut "discriminated unions," sangat tangguh dan aman secara tipe, memberikan jaminan compile-time tentang struktur data Anda berdasarkan properti literal umum (diskriminator).
Object.values() dengan as const dan Type Assertion
Saat menggunakan pola as const, Object.values() bisa sangat berguna. Namun, inferensi default TypeScript untuk Object.values() mungkin lebih luas dari yang diinginkan (misalnya, string[] alih-alih union literal tertentu). Anda mungkin memerlukan type assertion untuk ketegasan.
const Statuses = {
ACTIVE: "Aktif",
INACTIVE: "Tidak Aktif",
PENDING: "Tertunda",
} as const;
type StatusValue = typeof Statuses[keyof typeof Statuses]; // "Aktif" | "Tidak Aktif" | "Tertunda"
// Object.values(Statuses) disimpulkan sebagai (string | "Aktif" | "Tidak Aktif" | "Tertunda")[]
// Kita bisa menegaskannya lebih sempit jika perlu:
const allStatusValues: StatusValue[] = Object.values(Statuses);
console.log(allStatusValues); // ["Aktif", "Tidak Aktif", "Tertunda"]
// Untuk dropdown, Anda mungkin memasangkan nilai dengan label jika berbeda
const statusOptions = Object.entries(Statuses).map(([key, value]) => ({
value: key, // Gunakan kunci sebagai pengidentifikasi sebenarnya
label: value // Gunakan nilai sebagai label tampilan
}));
console.log(statusOptions);
/*
[
{ value: "ACTIVE", label: "Aktif" },
{ value: "INACTIVE", label: "Tidak Aktif" },
{ value: "PENDING", label: "Tertunda" }
]
*/
Ini menunjukkan cara mendapatkan array nilai yang diketik dengan kuat yang cocok untuk elemen UI sambil mempertahankan tipe literal.
Internasionalisasi (i18n) dan Label Terlokalisasi
Untuk aplikasi global, mengelola string yang dilokalkan adalah hal yang terpenting. Meskipun enum TypeScript dan alternatifnya menyediakan pengidentifikasi internal, label tampilan seringkali perlu dipisahkan untuk i18n. Pola as const secara indah melengkapi sistem i18n.
Anda mendefinisikan pengidentifikasi internal Anda yang tidak dapat diubah menggunakan as const. Pengidentifikasi ini konsisten di semua lokal dan berfungsi sebagai kunci untuk file terjemahan Anda. String tampilan yang sebenarnya kemudian diambil dari library i18n (misalnya, react-i18next, vue-i18n, FormatJS) berdasarkan bahasa yang dipilih pengguna.
// app/features/product/constants.ts
export const ProductCategories = {
ELECTRONICS: "ELECTRONICS",
APPAREL: "APPAREL",
HOME_GOODS: "HOME_GOODS",
BOOKS: "BOOKS",
} as const;
export type ProductCategory = typeof ProductCategories[keyof typeof ProductCategories];
// app/i18n/locales/en.json
{
"productCategories": {
"ELECTRONICS": "Electronics",
"APPAREL": "Apparel & Accessories",
"HOME_GOODS": "Home Goods",
"BOOKS": "Books"
}
}
// app/i18n/locales/id.json
{
"productCategories": {
"ELECTRONICS": "Elektronik",
"APPAREL": "Pakaian & Aksesori",
"HOME_GOODS": "Peralatan Rumah Tangga",
"BOOKS": "Buku"
}
}
// app/components/ProductCategorySelector.tsx
import { ProductCategories, type ProductCategory } from "../features/product/constants";
import { useTranslation } from "react-i18next";
function ProductCategorySelector() {
const { t } = useTranslation();
return (
<select>
{Object.values(ProductCategories).map(categoryKey => (
<option key={categoryKey} value={categoryKey}>
{t(`productCategories.${categoryKey}`)}
</option>
))}
</select>
);
}
Pemisahan kepentingan (separation of concerns) ini sangat penting untuk aplikasi global yang dapat diskalakan. Tipe TypeScript memastikan Anda selalu menggunakan kunci yang valid, dan sistem i18n menangani lapisan presentasi berdasarkan lokal pengguna. Ini menghindari string yang bergantung pada bahasa tertanam langsung dalam logika inti aplikasi Anda, sebuah anti-pola umum untuk tim internasional.
Kesimpulan: Memberdayakan Pilihan Desain TypeScript Anda
Seiring TypeScript terus berkembang dan memberdayakan pengembang di seluruh dunia untuk membangun aplikasi yang lebih tangguh dan dapat diskalakan, memahami fitur dan alternatifnya yang bernuansa menjadi semakin penting. Meskipun kata kunci enum TypeScript menawarkan cara mudah untuk mendefinisikan konstanta bernama, jejak runtime-nya, keterbatasan tree-shaking, dan kompleksitas pemetaan terbalik seringkali membuat alternatif modern lebih menarik untuk proyek yang sensitif terhadap performa atau berskala besar.
Union Type dengan Literal String/Numerik menonjol sebagai solusi yang paling ramping dan berpusat pada compile-time. Mereka memberikan keamanan tipe tanpa kompromi tanpa menghasilkan JavaScript apa pun saat runtime, menjadikannya ideal untuk skenario di mana ukuran bundle minimal dan tree-shaking maksimal menjadi prioritas, dan enumerasi runtime bukanlah masalah.
Di sisi lain, Const Assertion (as const) yang digabungkan dengan typeof dan keyof menawarkan pola yang sangat fleksibel dan kuat. Mereka menyediakan satu sumber kebenaran untuk konstanta Anda, keamanan tipe compile-time yang kuat, dan kemampuan penting untuk melakukan iterasi pada nilai saat runtime. Pendekatan ini sangat cocok untuk situasi di mana Anda perlu mengaitkan data tambahan dengan konstanta Anda, mengisi UI dinamis, atau berintegrasi secara mulus dengan sistem internasionalisasi.
Dengan mempertimbangkan secara cermat trade-off – jejak runtime, kebutuhan iterabilitas, dan kompleksitas data terkait – Anda dapat membuat keputusan yang tepat yang mengarah pada kode TypeScript yang lebih bersih, lebih efisien, dan lebih mudah dipelihara. Menerima alternatif-alternatif ini bukan hanya tentang menulis TypeScript "modern"; ini tentang membuat pilihan arsitektural yang disengaja yang meningkatkan performa aplikasi, pengalaman pengembang, dan keberlanjutan jangka panjang untuk audiens global.
Berdayakan pengembangan TypeScript Anda dengan memilih alat yang tepat untuk pekerjaan yang tepat, melampaui enum default ketika alternatif yang lebih baik ada.