Topik ini membahas pengetikan objek yang kuncinya baru diketahui saat program berjalan, menggunakan index signature dan tipe Record. Pembahasan mencakup penulisan keduanya, perbedaan kekakuannya, serta pola akses yang aman terhadap kunci yang belum tentu tersedia. Setelah topik ini dipelajari, data berkunci dinamis seperti stok barang dan rekap kehadiran dapat ditangani tanpa mengorbankan pemeriksaan tipe.
Index signature untuk kunci dinamis#
Tipe objek biasa menyebutkan nama propertinya satu per satu, dan cara ini tepat selama daftar propertinya tetap. Apabila nama kunci baru muncul saat program berjalan, misalnya nama barang yang dimasukkan petugas gudang, daftar yang tetap itu tidak lagi memadai. Index signature menyelesaikannya dengan menyatakan tipe untuk seluruh kunci sekaligus, ditulis di dalam kurung siku pada tipe objek.
Berkas stok.ts berikut memuat arsip inventaris yang kuncinya berkembang saat program berjalan.
interface StokInventaris {
[namaBarang: string]: number;
}
const stok: StokInventaris = {
kertas: 120,
spidol: 45,
};
stok["penghapus"] = 30;
for (const [namaBarang, jumlah] of Object.entries(stok)) {
console.log(`${namaBarang}: ${jumlah}`);
}kertas: 120
spidol: 45
penghapus: 30Tipe [namaBarang: string]: number menyatakan setiap kunci string dipetakan ke sebuah nilai number, sehingga penulisan stok["penghapus"] = 30 sah tanpa mengubah definisi tipe. Nama namaBarang di dalam kurung siku hanyalah label untuk pembaca manusia dan tidak memiliki efek apa pun. Akses terhadap kunci dinamis ditulis dengan notasi kurung siku seperti stok["kertas"], karena nama kuncinya baru diketahui saat program berjalan.
Tipe nilai pada index signature juga mengikat properti bernama apa pun pada objek itu, sehingga seluruh isi data tetap konsisten bertipe number. Properti bernama semacam itu tetap diperbolehkan berdampingan dengan index signature selama tipenya cocok. Aturan ini menutup kemungkinan satu objek memiliki kumpulan nilai yang tidak seragam tanpa disengaja.
Record sebagai tipe ringkas#
Tipe utilitas Record<K, V> menuliskan bentuk yang sama secara lebih ringkas, yaitu Record<string, number> setara dengan objek bertipe index signature string ke number. Kekuatan utamanya muncul ketika parameter K berupa union, yakni gabungan beberapa tipe literal, karena kelengkapan kunci menjadi wajib dan diperiksa saat kompilasi. Bentuk ini tepat untuk kumpulan kunci yang sudah diketahui sejak awal, seperti status kehadiran pada rekap kelas.
Berkas jadwal-piket.ts berikut memuat rekap kehadiran dengan tiga kunci yang wajib terisi.
type StatusKehadiran = "hadir" | "izin" | "sakit";
const rekapKelas: Record<StatusKehadiran, number> = {
hadir: 28,
izin: 2,
sakit: 1,
};
console.log(rekapKelas.hadir);
console.log(rekapKelas.izin);28
2Berkas jadwal-salah.ts berikut sengaja menghilangkan kunci sakit, sehingga berkas ini tidak dijalankan dan hanya diperiksa tipenya.
// tidak-dijalankan
type StatusKehadiran = "hadir" | "izin" | "sakit";
const rekapKelas: Record<StatusKehadiran, number> = {
hadir: 28,
izin: 2,
};# periksa kelengkapan kunci Record dengan pemeriksa tipe
tsc --noEmit --pretty false jadwal-salah.tsjadwal-salah.ts(4,7): error TS2741: Property 'sakit' is missing in type '{ hadir: number; izin: number; }' but required in type 'Record<StatusKehadiran, number>'.Error TS2741 menyebut kunci yang hilang beserta tipenya, sehingga kelalaian terdeteksi sebelum program berjalan. Pola Record dengan kunci union berbeda dari index signature yang longgar, karena index signature menerima kunci baru apa pun tanpa menuntut kelengkapan tertentu. Pilih index signature untuk kunci terbuka dan Record dengan kunci literal untuk kumpulan kunci yang tertutup.
Bentuk Record<string, number> dan index signature menghasilkan tipe yang setara, sehingga pemilihannya menjadi soal keterbacaan. Banyak tim lebih menyukai Record karena tulisannya pendek dan konsisten dengan tipe utilitas lain seperti Partial dan Pick. Keduanya boleh dipakai bergantian dalam satu basis kode tanpa menimbulkan ketidakcocokan tipe.
Pola akses yang aman#
Akses stok[nama] tetap bertipe number meskipun kunci tersebut belum pernah ditambahkan, sedangkan nilai yang sesungguhnya diperoleh saat program berjalan adalah undefined. Celah inilah yang menuntut pola akses yang aman. Tiga pola menutup celah tersebut, yaitu pemeriksaan keanggotaan dengan operator in, nilai cadangan dengan operator ??, dan iterasi menggunakan Object.entries() alih-alih menebak kunci.
Berkas akses-aman.ts berikut menerapkan pemeriksaan in dan nilai cadangan pada satu fungsi.
const stok: Record<string, number> = {
kertas: 120,
spidol: 45,
};
function jumlahStok(namaBarang: string): number {
if (namaBarang in stok) {
return stok[namaBarang];
}
return 0;
}
console.log(jumlahStok("kertas"));
console.log(jumlahStok("lampu"));
console.log(stok["spidol"] ?? 0);120
0
45Fungsi jumlahStok() mengembalikan 0 untuk kunci lampu yang tidak ada, tanpa menyebarkan undefined ke perhitungan berikutnya. Pemeriksaan in menjawab pertanyaan keberadaan kunci, sedangkan operator ?? memberi nilai cadangan ketika kunci tersedia tetapi nilainya undefined.
Iterasi dengan Object.entries() menghasilkan pasangan kunci dan nilai yang benar-benar ada, sehingga program tidak pernah membaca kunci yang tidak tersedia. Pola ini menggantikan kebiasaan menebak nama kunci seperti stok["kursi"] yang belum tentu tersedia. Apabila hanya nama kuncinya yang dibutuhkan, Object.keys() memberikan daftar kunci bertipe string[] yang aman untuk diulang.
Untuk proyek yang banyak mengolah data berkunci dinamis, flag atau opsi konfigurasi noUncheckedIndexedAccess menaikkan tipe hasil akses menjadi number | undefined sehingga kelalaian tertangkap pemeriksa tipe. Perlindungan semacam ini tidak mengubah program berjalan sama sekali, tetapi memindahkan penemuan kelalaian dari pengguna kepada pemeriksa tipe. Bagian berikut menunjukkan pesan yang dihasilkannya.
Berkas akses-langsung.ts berikut mengembalikan hasil akses indeks tanpa pemeriksaan, sehingga berkas ini tidak dijalankan dan hanya diperiksa dengan opsi tersebut.
// tidak-dijalankan
const stok: Record<string, number> = {
kertas: 120,
};
function jumlahStok(namaBarang: string): number {
return stok[namaBarang];
}# aktifkan pemeriksaan akses indeks yang ketat
tsc --noEmit --pretty false --noUncheckedIndexedAccess akses-langsung.tsakses-langsung.ts(7,3): error TS2322: Type 'number | undefined' is not assignable to type 'number'.
Type 'undefined' is not assignable to type 'number'.Error TS2322 menyatakan undefined tidak dapat dijadikan number, dan pesan ini muncul persis pada fungsi yang tidak memeriksa kuncinya. Perbaikannya mengikuti pola pada bagian sebelumnya, yaitu menambahkan pemeriksaan in atau nilai cadangan sebelum nilai dikembalikan. Pesan semacam ini hanya muncul bila opsi tersebut aktif, sehingga pemeriksaan bawaan membiarkan berkas yang sama lolos tanpa komentar apa pun.
Kesalahan umum#
Kesalahan pada topik ini umumnya berupa asumsi bahwa tipe menjamin keberadaan nilai. Laporan pemeriksa tipe terlihat bersih, padahal perilaku program berjalan menyimpang dari dugaan. Perbaikan pada tiap butir berikut kecil, tetapi mengubah cara membaca tipe objek berkunci dinamis secara permanen.
- Menganggap tipe
numbermenjamin nilai tersedia. Akses indeks pada kunci yang belum ada tetap menghasilkanundefinedsaat program berjalan. Gunakan pemeriksaanin, nilai cadangan??, atau aktifkannoUncheckedIndexedAccess. - Mencampur tipe nilai pada satu index signature. Tipe nilai berlaku untuk seluruh kunci, sehingga campuran
stringdannumberharus dinyatakan sebagai union dan setiap pembacaannya dipersempit lebih dahulu. - Mengira
Recorddengan kunci union bersifat sama longgarnya dengan index signature. BentukRecordmenuntut seluruh kunci terisi dan menolaknya dengan error TS2741 bila ada yang kurang.
Ringkasan#
- Index signature mengetik seluruh kunci objek sekaligus dan cocok untuk kunci yang ditentukan saat program berjalan.
- Tipe
Record<K, V>menuliskan bentuk yang sama, sedangkanKberupa union literal menuntut semua kunci terisi. - Akses indeks tanpa pemeriksaan dapat menghasilkan
undefinedtanpa terdeteksi, sehingga gunakanin, nilai cadangan??, atau iterasiObject.entries(). - Opsi
noUncheckedIndexedAccessmengubah tipe hasil akses menjadiT | undefinedagar kelalaian tertangkap pada saat kompilasi.
Referensi#
- Objects — TypeScript Handbook — Index signature pada tipe objek beserta aturan tipe nilainya.
- Utility Types — TypeScript Handbook — Tipe Record untuk objek berkunci dinamis.
- noUncheckedIndexedAccess — TypeScript tsconfig Reference — Opsi yang menambahkan undefined pada tipe hasil akses indeks.