Record: Kamus Type-Safe
Record membangun kamus yang type-safe dari union literal: setiap key wajib ada, dengan perbandingan jujur melawan index signature.
Masalah yang Diselesaikan Record
Kamu punya union status pesanan dan butuh label tampilannya di UI. Tanpa Record, kamu menulis object biasa: tidak ada yang menjamin semua status ter-cover, dan typo pada key lolos diam-diam sampai user melihat label yang salah. Record<K, V> membangun tipe object dengan key bertipe K dan value bertipe V. Ketika K adalah union literal, compiler memastikan setiap key ada dan tidak ada key fiktif yang nyelip. Hasilnya adalah kamus yang tidak mungkin setengah jadi.
Contoh: Kamus Status dan Izin Peran
type Status = "pending" | "diproses" | "dikirim" | "selesai";
const labelStatus: Record<Status, string> = {
pending: "Menunggu pembayaran",
diproses: "Sedang dikemas",
dikirim: "Dalam perjalanan",
selesai: "Pesanan selesai",
};
function tampilkan(s: Status): string {
return labelStatus[s]; // selalu string, tidak pernah undefined
}
type Peran = "admin" | "kasir" | "gudang";
const izin: Record<Peran, string[]> = {
admin: ["baca", "tulis", "hapus"],
kasir: ["baca", "tulis"],
gudang: ["baca"],
};
function boleh(peran: Peran, aksi: string): boolean {
return izin[peran].includes(aksi);
}Pola ini ideal untuk konfigurasi yang harus sinkron dengan union: tambah status baru dibatalkan ke Status, dan compiler langsung menuntut entri labelnya. Tidak ada status yang tampil tanpa label di production, dan tidak ada peran yang diam-diam kehilangan daftar izinnya. Lookup seperti labelStatus[s] selalu mengembalikan string, jadi kamu tidak perlu pengecekan undefined yang berulang.
Error Nyata: Key yang Hilang
const izinKurang: Record<Peran, string[]> = {
admin: ["baca", "tulis", "hapus"],
kasir: ["baca", "tulis"],
};
// Error: Type '{ admin: string[]; kasir: string[]; }' is not assignable
// to type 'Record<Peran, string[]>'.
// Property 'gudang' is missing in type
// '{ admin: string[]; kasir: string[]; }'
// but required in type 'Record<Peran, string[]>'.Error ini adalah fitur, bukan gangguan: ia menangkap konfigurasi yang tidak lengkap sebelum aplikasi berjalan. Bandingkan dengan object biasa tanpa anotasi, di mana key yang hilang baru ketahuan saat user dengan peran gudang mendapat undefined di layar dan fitur terlihat rusak. Aturan mainnya dua arah: key yang hilang ditolak, dan key yang tidak ada di union juga ditolak, seperti yang sudah dibahas di modul Pick dan Omit.
Record vs Index Signature
// Index signature: key bebas, cocok untuk data dinamis
const stokGudang: { [kode: string]: number } = { jkt: 120, bdg: 85 };
stokGudang["sby"]; // string bebas, hasilnya number (atau undefined di strict)
// Record dengan union literal: key terbatas dan wajib lengkap
type Hari = "senin" | "selasa" | "rabu" | "kamis" | "jumat";
const jamBuka: Record<Hari, string> = {
senin: "09:00-21:00",
selasa: "09:00-21:00",
rabu: "09:00-21:00",
kamis: "09:00-21:00",
jumat: "09:00-21:00",
};
jamBuka["senin"]; // string, dijamin adaPilih index signature untuk key yang tidak diketahui saat compile, misalnya id dari database atau kode yang dibuat user. Pilih Record dengan union literal untuk himpunan key yang sudah pasti: hari, status, peran, atau metode pembayaran. Aturan praktisnya: kalau kamu bisa menulis semua key yang valid sebagai union, Record hampir selalu pilihan yang lebih aman karena ia mengubah asumsi "key ini ada" menjadi jaminan compiler.
Catatan teknis:
Record<K, V>hanyalah mapped type{ [P in K]: V }. KarenaKdibatasi kekeyof any(string, number, atau symbol),Record<string, V>berperilaku mirip index signature, sedangkanRecord<UnionLiteral, V>menjadi kamus ketat yang wajib lengkap seperti contoh di atas. Dua wajah ini menjelaskan kenapaRecordkadang terasa longgar dan kadang terasa sangat ketat: semuanya tergantung apa yang kamu masukkan sebagaiK.
Tantangan
Biaya per Metode Bayar
Diberi type MetodeBayar = "tunai" | "qris" | "transfer", buat const biaya bertipe Record<MetodeBayar, { label: string; fee: number }> yang lengkap untuk ketiga metode (tunai fee 0, qris fee 700, transfer fee 4000). Lalu tulis fungsi getFee(m: MetodeBayar): number yang mengambil fee dari map tersebut.