mapped-typeskeyofgenericsMenengah5 mnt baca

Mapped Types

Ubah bentuk tipe secara programatik dengan {[K in keyof T]}, termasuk membuat Partial dan Readonly versimu sendiri.

Analogi: Cetakan Kue yang Mengikuti Adonan

Bayangkan kamu punya cetakan kue yang otomatis mengikuti bentuk adonan apa pun yang dimasukkan: adonan bulat jadi kue bulat, adonan kotak jadi kue kotak. Mapped types adalah cetakan seperti itu untuk tipe: kamu memberi satu tipe masukan, dan TypeScript membangun tipe baru dengan mengubah setiap propertinya mengikuti aturan yang kamu tulis.

Kenapa Mapped Types Ada?

Tanpa mapped types, setiap variasi bentuk objek harus ditulis manual. Punya tipe User lalu butuh versi "semua opsional" untuk form edit? Tulis ulang semua properti dengan tanda ?. Butuh versi "semua read-only"? Tulis ulang lagi dengan readonly. Setiap ada properti baru di User, semua salinannya harus diupdate manual, dan satu saja yang lupa langsung jadi bug.

Sintaks dasarnya terlihat seperti loop:

typescript
type Contoh<T> = {
  [K in keyof T]: T[K];
};

Bacanya: "untuk setiap kunci K di dalam keyof T, buat properti K dengan tipe T[K]". keyof T menghasilkan union semua nama properti, in mengiterasinya, dan T[K] mengambil tipe tiap properti. Dari pola satu baris ini, kamu bisa menurunkan semua utility type terkenal.

Contoh 1: Membuat Partial Sendiri

Partial<T> bawaan TypeScript membuat semua properti opsional. Dengan mapped types, kamu bisa membuatnya sendiri dalam tiga baris dan langsung paham cara kerjanya:

typescript
type MyPartial<T> = {
  [K in keyof T]?: T[K];
};

interface User {
  id: number;
  nama: string;
  email: string;
}

type UserDraft = MyPartial<User>;
// sama dengan: { id?: number; nama?: string; email?: string; }

Tanda ? setelah [K in keyof T] adalah modifier yang diterapkan ke setiap properti hasil iterasi. Sekarang UserDraft cocok untuk form edit yang belum lengkap, dan kalau User tambah properti baru, UserDraft ikut update otomatis tanpa disentuh.

Contoh 2: Readonly Kustom dan Error Nya

Readonly<T> bawaan membuat semua properti tidak bisa diubah. Versi buatannya sendiri:

typescript
type MyReadonly<T> = {
  readonly [K in keyof T]: T[K];
};

interface Config {
  apiUrl: string;
  timeout: number;
}

const config: MyReadonly<Config> = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
};

config.timeout = 10000; // SALAH: properti ini read-only

Compiler langsung protes:

error TS2540: Cannot assign to 'timeout' because it is a read-only property.

Error TS2540 adalah bukti modifier readonly benar-benar menempel di setiap properti hasil mapping, bukan cuma tempelan di permukaan. Pola ini berguna untuk objek konfigurasi atau state yang tidak boleh dimutasi setelah dibuat.

Kamu juga bisa menggabungkan modifier: { -readonly [K in keyof T]?: T[K] } menghapus readonly sekaligus membuat opsional. Tanda minus di depan modifier artinya "cabut modifier itu".

Catatan teknis: Mapped types termasuk homomorphic, artinya kalau T adalah tipe objek biasa, hasil mapping mempertahankan modifier aslinya kecuali kamu mengubahnya eksplisit. Inilah alasan Partial<T> bawaan tetap menghormati properti readonly yang sudah ada di T: mapping tidak menghapus apa pun tanpa perintah. Perilaku ini juga yang membuat MyPartial di atas aman dipakai berlapis dengan tipe lain.

Kesalahan Umum

1. Lupa bahwa mapped type butuh tipe objek sebagai masukan

typescript
// SALAH: keyof string tidak menghasilkan kunci yang berguna
type Rusak = { [K in keyof string]: string };

// BENAR: masukkan selalu tipe objek / interface
interface User { nama: string }
type Betul = { [K in keyof User]?: User[K] };

2. Menulis tipe properti manual, bukan T[K]

typescript
interface Produk { nama: string; harga: number }

// SALAH: semua properti jadi string, informasi tipe hilang
type Salah = { [K in keyof Produk]: string };

// BENAR: T[K] menjaga tipe asli tiap properti
type Benar = { [K in keyof Produk]: Produk[K] };

3. Mengira mapped type mengubah objek saat runtime

typescript
// SALAH: "MyReadonly membuat objek tidak bisa diubah saat jalan"
// Faktanya readonly hanya ada saat compile; di JavaScript hasil
// compile tidak ada perlindungan apa pun.

// BENAR: untuk proteksi runtime pakai Object.freeze()
const aman = Object.freeze({ timeout: 5000 });

Tantangan

Bangun Pick Versimu Sendiri

TypeScript punya Pick<T, K> bawaan yang mengambil sebagian properti. Buat versimu sendiri: type MyPick<T, K extends keyof T> = { [P in K]: T[P] }. Terapkan ke interface User untuk membuat tipe UserPreview yang hanya berisi id dan nama, lalu buktikan compiler menolak properti lain.