Operator keyof dan typeof bekerja pada posisi tipe, yaitu tempat tipe dituliskan dalam kode. Keduanya memungkinkan tipe diturunkan dari nilai yang sudah ada, sehingga deklarasi tipe tidak perlu ditulis ulang secara terpisah. typeof mengambil tipe dari sebuah nilai, sedangkan keyof menghasilkan gabungan seluruh nama properti dari sebuah tipe. Topik ini membahas keduanya secara berurutan, lalu merangkainya menjadi pola akses properti yang aman dari kesalahan ketik.
Operator keyof pada tipe#
Operator tipe keyof T menghasilkan union berisi seluruh nama properti milik T dalam bentuk literal. Hasilnya murni berada di level tipe, yaitu tidak menghasilkan nilai apa pun saat program berjalan, tetapi daftar kuncinya ikut berubah ketika tipe asalnya berubah. Operator ini menjadi dasar banyak alat bantu tipe karena daftar kunci yang sah kini diturunkan, bukan disalin.
Pemakaian keyof paling sering muncul pada konstanta konfigurasi yang deklarasinya ditandai as const. Penanda as const menjadikan objek bersifat tetap secara mendalam, sehingga tiap nilainya diperlakukan sebagai tipe literal dan tidak melebar menjadi string atau number. Dengan kombinasi tersebut, daftar kunci dan tipe nilainya berasal dari satu sumber yang sama.
const PENGATURAN = {
interval: 5,
kanal: "A2",
batas: 40,
} as const;
type Kunci = keyof typeof PENGATURAN;
function ambil<K extends Kunci>(kunci: K): (typeof PENGATURAN)[K] {
return PENGATURAN[kunci];
}
console.log(ambil("interval"), ambil("kanal"), ambil("batas"));
console.log(Object.keys(PENGATURAN).length);5 A2 40
3Tipe Kunci di atas setara dengan "interval" | "kanal" | "batas", yang diturunkan dari objek PENGATURAN melalui keyof typeof. Fungsi ambil membatasi parameternya dengan K extends Kunci, sehingga hanya nama properti yang benar-benar ada yang diterima. Tipe kembaliannya ditulis (typeof PENGATURAN)[K], yaitu tipe properti yang sesuai dengan kunci yang diminta, sehingga ambil("kanal") diperlakukan bertipe "A2" dan bukan string.
Hasil keyof tidak terbatas pada tipe yang dideklarasikan sendiri, karena tipe bawaan pun memiliki kunci. Pada tipe array, hasilnya memuat indeks beserta nama properti seperti length, sedangkan pada kelas, hasilnya memuat nama seluruh propertinya. Kunci berupa simbol juga ikut dihasilkan apabila tipe sumber memilikinya, sehingga union hasil keyof selalu mengikuti isi tipe asal.
Operator typeof pada posisi tipe#
Kata kunci typeof memiliki dua peran berbeda dalam TypeScript. Pada posisi ekspresi, typeof x adalah operator JavaScript yang mengembalikan nama tipe nilai saat program berjalan. Pada posisi tipe, sebagaimana dalam type Arsip = typeof arsip, operator ini dihitung oleh compiler dan menghasilkan tipe lengkap dari nilai arsip. Konstruksinya hilang seluruhnya saat program dikompilasi.
type Expect<T extends true> = T;
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
const arsip = {
id: "arsip-001",
berkas: 120,
terkunci: false,
};
type Arsip = typeof arsip;
type KunciArsip = keyof Arsip;
const bukti1: Expect<Equal<Arsip, { id: string; berkas: number; terkunci: boolean }>> = true;
const bukti2: Expect<Equal<KunciArsip, "id" | "berkas" | "terkunci">> = true;
console.log(arsip.berkas, bukti1, bukti2);120 true trueTipe Arsip memuat ketiga properti dengan tipenya masing-masing, dan keyof Arsip menghasilkan ketiga nama propertinya, sebagaimana dibuktikan pola Expect dengan Equal. Kedua tipe itu diturunkan dari konstanta yang sama, sehingga penambahan properti pada arsip otomatis diikuti oleh tipe Arsip dan KunciArsip. Sinkronisasi manual antara nilai dan tipe tidak lagi diperlukan.
Operator ini juga bekerja pada fungsi, sehingga tanda tangan sebuah fungsi dapat diturunkan dari implementasinya. Menulis type FungsiAmbil = typeof ambil pada contoh sebelumnya menghasilkan tipe fungsi lengkap dengan parameter dan kembaliannya. Satu deklarasi pun melayani dua kebutuhan, yaitu kode yang berjalan dan tipe yang dipakai ulang di tempat lain.
Akses properti yang aman#
Rangkaian keyof dan typeof menyelesaikan masalah akses properti melalui kunci yang berasal dari luar, misalnya dari argumen fungsi atau hasil pembacaan data. Tanpa bantuan tipe, kunci semacam itu bertipe string dan aksesnya tidak dapat diperiksa. Dengan membatasi parameter kunci pada keyof, compiler menolak kunci yang tidak ada dan menampilkan daftar kunci yang sah.
// tidak-dijalankan
const PENGATURAN = {
interval: 5,
kanal: "A2",
batas: 40,
} as const;
type Kunci = keyof typeof PENGATURAN;
function ambil<K extends Kunci>(kunci: K): (typeof PENGATURAN)[K] {
return PENGATURAN[kunci];
}
console.log(ambil("gaya"));# kunci gaya tidak ada pada PENGATURAN
tsc --noEmit --pretty false kunci-salah.tskunci-salah.ts(14,19): error TS2345: Argument of type '"gaya"' is not assignable to parameter of type '"batas" | "interval" | "kanal"'.Pesan error memuat daftar kunci yang sah, sehingga perbaikannya terlihat tanpa membuka berkas lain. Manfaat yang sama muncul pada pengubahan nama properti, karena kunci yang lama langsung dilaporkan pada setiap pemanggilan yang belum disesuaikan. Pola yang sama menjadi fondasi utilitas seperti Pick dan Record yang bekerja atas kunci sebuah tipe.
Keamanan serupa dirasakan pada proses pemeliharaan jangka panjang. Kunci lama yang masih dipakai di titik pemanggilan dilaporkan compiler satu per satu, sehingga tidak ada pemanggilan yang terlewat tanpa diketahui. Pengubahan nama menjadi operasi yang dapat dipercaya pada tipe yang diturunkan, dan bukan pencarian teks yang bergantung pada ketelitian.
Kesalahan umum#
Kekeliruan pertama adalah mencampuradukkan kedua peran typeof. Pada posisi tipe, typeof tidak mengeksekusi apa pun dan tidak menghasilkan nilai saat program berjalan, karena seluruh konstruksinya dihapus oleh compiler. Menuntut typeof pada posisi tipe untuk menghitung sesuatu menandakan bahwa nilai tersebut seharusnya didefinisikan terlebih dahulu, lalu tipenya diturunkan darinya.
Kekeliruan kedua adalah melupakan as const pada konstanta yang ingin diturunkan tipenya. Tanpa penanda tersebut, properti kanal pada PENGATURAN bertipe string dan bukan "A2", sehingga presisi literal yang menjadi kekuatan pola ini hilang. Kekeliruan ketiga adalah menyalin daftar kunci secara manual ke dalam tipe terpisah, sehingga salinan itu tidak lagi sinkron dengan data aslinya, padahal keyof typeof otomatis mengikuti setiap perubahan nilai asalnya.
Ringkasan#
keyof Tmenghasilkan union nama properti milikT, sehingga daftar kunci yang sah diturunkan dan bukan disalin.typeofpada posisi tipe mengambil tipe lengkap dari sebuah nilai dan dihapus compiler saat program berjalan.- Kombinasi
as constdengankeyof typeofmenjadikan konstanta objek sebagai satu-satunya sumber kunci dan tipe nilainya. - Batasan
K extends keyof Tdengan kembalianT[K]menghasilkan akses properti yang aman, dan kunci yang tidak ada ditolak beserta daftar kuncinya.
Referensi#
- Keyof Type Operator — The TypeScript Handbook — Menjelaskan operator keyof dan hasilnya pada berbagai bentuk tipe.
- Typeof Type Operator — The TypeScript Handbook — Menguraikan typeof pada posisi tipe beserta batasannya.