keyofgenerictipe-lanjutanMenengah3 mnt baca

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

typescript
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:

typescript
// 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:

typescript
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:

typescript
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: keyof juga fondasi mapped types: { [P in keyof T]?: T[P] } adalah definisi Partial<T>. Setelah paham keyof, kamu selangkah lagi bisa menulis utility type sendiri. Dan keyof typeof obj menggabungkan 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[]).

typescript
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]