Mapped Type di TypeScript: Mengubah Setiap Key

Pelajari cara mapped type di TypeScript membentuk tipe baru dengan mengubah tiap properti dari tipe yang sudah ada, dan bagaimana Partial serta Readonly dibuat dengan cara ini.

Diterbitkan 20 September 20269 menit bacaOleh ACY Partner Indonesia
Sampul Mapped Type dengan sintaks [K in keyof T]
300 × 250Slot Iklan TersediaPasang iklan Anda di sini

Bayangkan kamu punya sebuah tipe yang menggambarkan seorang user. Lalu kamu butuh versi kedua dari tipe itu, tapi semua field-nya jadi opsional. Setelah itu butuh versi ketiga lagi, kali ini semua field-nya read-only alias tidak bisa diubah. Kamu bisa saja menyalin tipenya dan mengeditnya satu per satu, tapi cara itu cepat melelahkan. Lebih buruk lagi, begitu seseorang mengubah salah satu salinan dan lupa menyesuaikan yang lain, kedua versi itu langsung tidak sinkron.

TypeScript punya jawaban yang jauh lebih rapi: mapped type. Mapped type adalah semacam resep yang mengambil sebuah tipe lalu membangun tipe baru dengan menelusuri tiap properti dan mengubahnya. Begitu kamu paham polanya, banyak “keajaiban” bawaan TypeScript yang tadinya terasa misterius jadi masuk akal. Di artikel ini kita akan jalan pelan-pelan, menyusun idenya dari potongan-potongan kecil, dan menutupnya dengan satu contoh lengkap yang bisa kamu telusuri baris demi baris.

Dua bahan yang perlu kamu kuasai dulu

Sebelum mapped type terasa masuk akal, ada dua perkakas kecil yang harus klik di kepala. Keduanya sederhana, jadi kita bereskan dulu.

keyof memberi kamu daftar key sebagai tipe

Ketika kamu menulis keyof SomeType, TypeScript mengembalikan sebuah tipe yang berisi seluruh nama properti (key) dari tipe itu, digabung dengan tanda | (dibaca “atau”).

type User = {
  name: string;
  email: string;
  age: number;
};

type UserKeys = keyof User;
// UserKeys adalah: "name" | "email" | "age"

Jadi UserKeys bukan nilai yang bisa kamu jalankan, melainkan sebuah tipe yang artinya “salah satu dari ketiga teks ini”. Sebuah variabel bertipe UserKeys hanya boleh berisi "name", "email", atau "age".

Indexed access membaca tipe sebuah properti

Perkakas kedua bentuknya mirip mengakses elemen array, tapi ini bekerja pada tipe. User["email"] memberi kamu tipe dari properti email, bukan nilainya.

type EmailType = User["email"]; // string
type AgeType = User["age"];     // number

Kamu bahkan bisa menggabungkannya dengan keyof untuk mengambil tipe dari semua properti sekaligus:

type AnyValue = User[keyof User]; // string | number

Baris itu artinya “tipe dari nilai apa pun yang ada di balik key mana pun pada User”. Pegang dua ide ini baik-baik: keyof untuk mengambil key-nya, indexed access untuk mengambil tipe nilainya. Mapped type sebenarnya cuma keduanya bekerja bersama di dalam sebuah perulangan.

Bentuk dasar sebuah mapped type

Ini pola intinya. Baca pelan-pelan:

type Example = {
  [K in keyof User]: User[K];
};

Mari kita bedah kata per kata:

  • [K in keyof User] adalah perulangannya. Artinya: untuk tiap key di User, biarkan K mewakili key itu, satu per satu.
  • : User[K] adalah hasil untuk tiap key. Untuk key K yang sedang diproses, ambil tipenya dari User.

Mapped type yang satu ini membangun ulang User persis seperti aslinya, tiap key mempertahankan tipe asalnya. Sendirian, ini memang belum berguna, tapi ia membuktikan mekanismenya: kita sudah mengunjungi tiap properti dan menentukan ia menjadi apa. Kekuatannya muncul begitu kita mengubah sisi kanan atau menambahkan sebuah modifier.

{ [K in keyof T]: <tipe baru untuk K> }
   |        |          |
   |        |          +-- key ini menjadi apa
   |        +------------- tipe sumber yang kita baca
   +---------------------- variabel perulangan (satu key tiap putaran)

K cuma sekadar nama

Huruf K itu kebiasaan saja, singkatan dari “key”, tapi sebenarnya hanya nama variabel. Kamu bisa menulis [Prop in keyof User] dan hasilnya sama persis. Pilih nama mana pun yang paling enak dibaca.

Mengubah nilainya

Sekarang kita benar-benar mengubah sesuatu. Misalkan kamu ingin sebuah tipe yang setiap field-nya User menjadi boolean, bentuk yang umum dipakai untuk melacak field mana saja di sebuah form yang sudah disentuh pengguna.

type TouchedFields = {
  [K in keyof User]: boolean;
};
// {
//   name: boolean;
//   email: boolean;
//   age: boolean;
// }

Kita mempertahankan key-nya tapi membuang tipe nilai aslinya, menggantinya dengan boolean. Struktur User ikut terbawa secara otomatis. Kalau nanti seseorang menambahkan field phone: string ke User, TouchedFields langsung kebagian phone: boolean tanpa kita sentuh, karena ia dihasilkan, bukan disalin.

Supaya bisa dipakai ulang untuk tipe apa pun, bungkus dalam sebuah generic. T di sini adalah tempat kosong yang diisi saat tipe itu digunakan:

type AllBooleans<T> = {
  [K in keyof T]: boolean;
};

type TouchedUser = AllBooleans<User>;

Sekarang AllBooleans bisa bekerja untuk sebuah produk, sebuah order, apa saja. Persis seperti inilah TypeScript menyusun perkakas bawaannya sendiri, seperti yang akan kita lihat sebentar lagi.

Modifier: menambah dan melepas optional serta readonly

Mapped type bisa lebih dari sekadar mengganti tipe nilai. Ia bisa menambah atau melepas dua modifier properti: tanda ? yang menandai properti sebagai opsional, dan readonly yang mencegah properti ditimpa ulang.

Untuk menambah sebuah modifier, tulis ia di depan key. Untuk melepas modifier yang sudah ada, beri tanda minus di depannya: -? atau -readonly.

// Buat semua properti jadi opsional
type Optional<T> = {
  [K in keyof T]?: T[K];
};

// Buat semua properti jadi wajib (lepas tanda ?)
type Required<T> = {
  [K in keyof T]-?: T[K];
};

// Buat semua properti jadi read-only
type Frozen<T> = {
  readonly [K in keyof T]: T[K];
};

Tanda ? setelah [K in keyof T] itulah seluruh rahasia di balik membuat field jadi opsional. Dipadukan dengan T[K] yang menjaga tipe nilai aslinya, kamu mendapat salinan “semua opsional” yang setia dari tipe apa pun.

Kamu sudah pernah memakainya

Kalau kamu pernah menulis Partial<User> atau Readonly<User>, sebenarnya kamu sudah memakai mapped type tanpa sadar. Keduanya bukan kata kunci khusus dari kompiler, melainkan tipe biasa yang didefinisikan di pustaka standar TypeScript memakai pola yang persis seperti di atas.

Bagaimana Partial dan Readonly sebenarnya dibuat

Bagian inilah yang menyatukan semuanya. Utility type bawaan yang kamu pakai tiap hari ternyata cuma mapped type pendek. Definisi aslinya pada dasarnya seperti ini:

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

type Required<T> = {
  [K in keyof T]-?: T[K];
};

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

Bandingkan tiap baris dengan pola yang sudah kita bangun:

Utility Yang dilakukan mapped type-nya
Partial<T> Telusuri tiap key, tambahkan ? supaya jadi opsional
Required<T> Telusuri tiap key, pakai -? untuk melepas opsional
Readonly<T> Telusuri tiap key, tambahkan readonly ke masing-masing

Tidak ada mekanisme tersembunyi. Begitu mapped type sudah masuk akal, definisi-definisi ini terbaca seperti kalimat biasa: “untuk tiap key, pertahankan tipenya, tapi tandai opsional.” Itulah inti dari mempelajari polanya, ia menyingkap sekaligus sebagian besar isi pustaka standar TypeScript.

Satu contoh lengkap, langkah demi langkah

Mari kita susun sesuatu yang sedikit lebih menarik dan telusuri tiap barisnya. Misalkan kita punya objek pengaturan, dan kita ingin sebuah tipe yang tiap field-nya berubah menjadi fungsi getter yang mengembalikan tipe nilai aslinya. Jadi theme: string mestinya berubah menjadi getTheme: () => string.

Kita butuh dua hal sekaligus: mengubah nilai (membungkusnya jadi fungsi) dan mengganti nama key (menambahkan get lalu mengawalinya dengan huruf besar). Penggantian nama memakai klausa as setelah perulangan, dipadukan dengan template literal type.

type Settings = {
  theme: string;
  fontSize: number;
  notifications: boolean;
};

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type SettingsGetters = Getters<Settings>;
// {
//   getTheme: () => string;
//   getFontSize: () => number;
//   getNotifications: () => boolean;
// }

Sekarang mari kita urai baris tengah yang padat itu, [K in keyof T as ...]:

  1. [K in keyof T — perulangan yang sudah akrab: kunjungi tiap key dari T, satu per satu, sebut ia K.
  2. as — memulai penggantian nama key. Apa pun yang mengikutinya menjadi nama key yang baru.
  3. `get${Capitalize<string & K>}` — sebuah template literal type. Ia menempelkan teks get di depan key. Capitalize<...> membuat huruf pertama jadi kapital, jadi theme menjadi Theme. Bagian string & K adalah langkah pengaman kecil yang memberi tahu TypeScript untuk memperlakukan K sebagai string supaya Capitalize mau menerimanya.
  4. : () => T[K] — tipe nilai yang baru. Untuk key yang sedang diproses, hasilnya adalah fungsi tanpa argumen yang mengembalikan tipe asli key tersebut, yaitu T[K].

Disatukan, tiap key seperti theme: string ditulis ulang menjadi getTheme: () => string. Key-nya berubah, nilainya berubah, dan kita cuma menulis aturannya satu kali. Tambahkan satu field ke Settings, getter yang sesuai muncul dengan sendirinya.

Penggantian nama key fitur yang lebih baru

Klausa as untuk mengganti nama key ditambahkan di versi TypeScript yang lebih baru dibanding mapped type dasar. Bentuk polos [K in keyof T] sudah ada jauh lebih lama. Kalau penggantian nama key memunculkan error yang tak terduga, periksa apakah versi TypeScript-mu sudah cukup baru, sebelum buru-buru menyimpulkan sintaksnya salah.

Kapan sebaiknya memakai mapped type

Mapped type bersinar ketika dua salinan dari sebuah tipe seharusnya tetap sinkron, tapi terpaksa kamu jaga manual. Beberapa pertanda jujurnya:

  • Kamu terus-menerus menulis tipe yang nyaris identik dan cuma berbeda di ?, readonly, atau tipe nilai yang seragam.
  • Kamu ingin sebuah tipe memperbarui dirinya sendiri otomatis ketika tipe sumbernya bertambah atau berkurang field.
  • Sebuah pustaka meminta “bentuk yang sama, tapi semua opsional” atau “key yang sama, tapi dipetakan ke handler”.

Dan satu pesan untuk menahan diri: kalau kamu cuma butuh satu tipe tetap yang tidak akan berubah, tulis saja apa adanya. Mapped type baru sepadan kalau ada pemakaian ulang dan kebutuhan untuk tetap sinkron dengan tipe sumber. Memaksakan versi yang canggih padahal objek biasa sudah cukup hanya membuat kode lebih sulit dibaca orang berikutnya.

Rangkuman

Mapped type adalah salah satu fitur yang kelihatannya menyeramkan padahal ternyata kecil begitu kamu melihat bagian-bagiannya. Ini seluruh idenya dalam beberapa baris:

  • keyof T memberi kamu gabungan key sebuah tipe, dalam bentuk tipe.
  • T[K] (indexed access) memberi kamu tipe dari sebuah properti.
  • Mapped type, { [K in keyof T]: ... }, menelusuri key-key itu dan menentukan masing-masing menjadi apa.
  • Modifier ? dan readonly bisa ditambahkan langsung, atau dilepas dengan -? dan -readonly.
  • Partial, Required, dan Readonly bawaan TypeScript hanyalah mapped type ini, tidak lebih.
  • Sebuah klausa as dengan template literal type memungkinkan kamu mengganti nama key sambil mengubahnya.

Lain kali kamu melihat Partial<User> di sebuah proyek, kamu akan tahu persis apa yang terjadi di baliknya: sebuah perulangan kecil, mengunjungi tiap key, menambahkan tanda tanya. Mulailah dari utility bawaan, lalu tulis versimu sendiri pada hari ketika kamu mendapati dirimu menyalin sebuah tipe dan mengeditnya manual. Saat itulah sebuah mapped type membayar dirinya sendiri.

Tag:typescriptmapped-typekeyofutility-typefrontend
728 × 90Slot Iklan TersediaPasang iklan Anda di sini

Artikel Terkait

Lihat Semua Artikel

Artikel yang Mungkin Kamu Suka

Apa Itu Framework Frontend? — chip kode dengan tag komponen
Frontend / Dasar

Apa Itu Framework Frontend?

Penjelasan ramah untuk pemula tentang apa itu framework frontend, kenapa mereka ada, masalah apa yang mereka selesaikan dibanding JavaScript biasa, dan kapan kamu memang sebaiknya memakainya.

20 Sep 20268 menit baca
Sampul fundamentals frontend bertuliskan Apa Itu Frontend dan HTML + CSS + JS
Frontend / Dasar

Apa Itu Frontend Development?

Panduan ramah pemula tentang frontend development: bagian website yang kamu lihat dan klik secara langsung. Pahami cara kerja HTML, CSS, dan JavaScript, serta bedanya frontend dengan backend.

20 Sep 20268 menit baca
Ilustrasi sampul untuk Apa yang Dikerjakan Frontend Developer
Frontend / Dasar

Apa yang Dikerjakan Frontend Developer: Panduan untuk Pemula

Penjelasan ramah pemula tentang apa yang sebenarnya dikerjakan frontend developer setiap hari: mengubah desain jadi antarmuka yang berfungsi, membangun UI yang bisa dipakai ulang, sampai urusan responsif, aksesibilitas, dan kecepatan.

20 Sep 20269 menit baca
Kartu judul bertuliskan Styling dan Interaktivitas di atas latar biru gelap ACY Partner
Frontend / Dasar

Cara Kerja Styling dan Interaktivitas

Penjelasan ramah pemula soal bagaimana CSS menyulap HTML polos jadi tampilan rapi, dan bagaimana JavaScript menambahkan perilaku — pola berpikir struktur, gaya, perilaku saat membangun halaman web.

20 Sep 20268 menit baca
Tiga lapisan front-end menyatu di satu halaman web
Frontend / Dasar

Cara HTML, CSS, dan JavaScript Bekerja Bersama

Panduan praktis untuk pemula: bikin satu tombol kecil, beri struktur dengan HTML, tampilan dengan CSS, dan perilaku dengan JavaScript, lalu lihat ketiga lapisan ini bekerja sama di halaman nyata.

20 Sep 20268 menit baca
Sampul ikhtisar praktik terbaik frontend
Frontend / Dasar

Ikhtisar Praktik Terbaik Frontend

Peta ringkas tentang apa itu frontend yang baik — HTML bermakna, CSS rapi, JavaScript secukupnya, desain responsif, performa, dan progressive enhancement, lengkap dengan arahan untuk mendalaminya.

20 Sep 20268 menit baca