Operator keyof
Ambil union semua key dari sebuah tipe: keyof, fondasi get() yang type-safe.
Daftar Kunci Milik Tipe
keyof adalah operator yang mengekstrak "daftar kunci" dari sebuah tipe dalam bentuk union string literal. Analoginya seperti meminta daftar isi lemari: kamu tidak mengambil bajunya, hanya daftar labelnya. Kenapa ini berguna? Karena banyak operasi (ambil properti, update field form, validasi) bekerja pada NAMA properti, dan nama-nama itu seharusnya diperiksa compiler, bukan ditulis sebagai string bebas yang rawan typo.
Dasar: keyof Menghasilkan Union
interface User {
nama: string;
umur: number;
aktif: boolean;
}
type KunciUser = keyof User;
// "nama" | "umur" | "aktif"
function cetakKunci(k: KunciUser): void {
console.log(k);
}
cetakKunci("nama"); // OK
cetakKunci("email");
// Error: Argument of type '"email"' is not assignable to
// parameter of type 'keyof User'.Tanpa keyof, parameter itu akan bertipe string dan typo "emial" lolos diam-diam sampai fitur rusak di tangan user.
Kasus Klasik: get() yang Type-Safe
Skenario nyata: kamu butuh fungsi get(obj, kunci) generik, misal untuk mengakses field form secara dinamis. Versi naive memakai any dan kehilangan semua perlindungan:
// Versi berbahaya: lolos compile, meledak saat runtime
function getLepas(obj: object, kunci: string): any {
return (obj as any)[kunci];
}
const x = getLepas({ nama: "Budi" }, "salah"); // any, undefined diam-diam
// Versi aman: keyof + generic + indexed access
function get<T, K extends keyof T>(obj: T, kunci: K): T[K] {
return obj[kunci];
}
const user = { nama: "Budi", umur: 17 };
const nama = get(user, "nama"); // string, bukan any!
const umur = get(user, "umur"); // number
get(user, "salah");
// Error: Argument of type '"salah"' is not assignable to
// parameter of type '"nama" | "umur"'.Ada tiga pemain di sini: T menangkap tipe object, K extends keyof T memastikan kunci valid DAN meng-infer kunci spesifik yang diminta, dan T[K] (indexed access type) menghitung tipe return dari pasangan itu. Hasilnya: return type mengikuti kunci yang diminta, "nama" memberi string, "umur" memberi number. Ini mustahil dilakukan dengan overload manual untuk object besar.
Skenario Nyata: Update Field Form
Di aplikasi form, kamu sering update satu field berdasarkan namanya. Tanpa keyof, handler-nya longgar:
interface FormProfil {
nama: string;
umur: number;
bio: string;
}
function updateField<K extends keyof FormProfil>(
form: FormProfil,
field: K,
value: FormProfil[K]
): FormProfil {
return { ...form, [field]: value };
}
const f1: FormProfil = { nama: "A", umur: 17, bio: "-" };
const f2 = updateField(f1, "umur", 18); // OK, value harus number
const f3 = updateField(f1, "umur", "tua");
// Error: Argument of type 'string' is not assignable to
// parameter of type 'number'.Perhatikan f3: bukan hanya nama field yang diperiksa, tapi VALUE-nya juga harus cocok dengan tipe field tersebut. Salah ketik nama field ditolak, salah tipe value ditolak. Satu signature menggantikan puluhan setter manual.
Contoh Error Nyata: keyof pada Union
Perilaku yang sering mengejutkan: keyof pada union hanya menghasilkan key yang ada di SEMUA anggota:
interface Kucing { nama: string; meong(): void; }
interface Anjing { nama: string; gonggong(): void; }
type K = keyof (Kucing | Anjing); // "nama" saja! meong/gonggong hilang
function panggil<K2 extends keyof (Kucing | Anjing)>(h: Kucing | Anjing, k: K2) {
return h[k];
}
panggil({ nama: "Mimi", meong() {} }, "meong");
// Error: Argument of type '"meong"' is not assignable to
// parameter of type '"nama"'.Ini logis kalau dipikir: compiler tidak bisa menjamin meong ada karena nilainya bisa jadi Anjing. Kalau butuh key dari salah satu anggota, sebarkan dulu union-nya atau tangani tiap anggota terpisah. Memahami ini mencegah kebingungan "kok key saya hilang".
Catatan teknis:
keyofjuga fondasi mapped types:{ [P in keyof T]?: T[P] }adalah definisiPartial<T>. Setelah paham keyof, kamu selangkah lagi bisa menulis utility type sendiri. Dankeyof typeof objmenggabungkan dua modul: ambil key dari tipe yang "dicuri" sebuah value.
Tantangan
Pluck yang aman
Buat fungsi generic pluck<T, K extends keyof T>(arr: T[], kunci: K): T[K][] yang mengambil satu properti dari tiap object dalam array. Test dengan array 3 user, pluck "nama" (hasil string[]) dan "umur" (hasil number[]).
function pluck<T, K extends keyof T>(arr: T[], kunci: K): T[K][] {
// petik tiap object
}
const users = [
{ nama: "A", umur: 20 },
{ nama: "B", umur: 21 },
{ nama: "C", umur: 22 },
];
console.log(pluck(users, "nama")); // ["A","B","C"]
console.log(pluck(users, "umur")); // [20,21,22]