Pick dan Omit
Pick mengambil subset field dan Omit membuangnya: cara aman menurunkan DTO publik dari entity internal tanpa menulis ulang interface.
Kenapa Tidak Menulis Ulang Interface?
Satu entity User melahirkan banyak varian: data internal lengkap dengan passwordHash, data publik untuk API, ringkasan untuk kartu daftar, input untuk form registrasi. Menulis interface baru untuk tiap varian berarti setiap perubahan di entity harus diingat ke semua varian, dan manusia pasti lupa. Lupa di sini berbahaya: field sensitif yang baru ditambahkan bisa bocor ke API publik hanya karena varian duplikatnya tidak ikut diupdate. Pick<T, K> mengambil subset key, Omit<T, K> membuang subset key. Varian didefinisikan sebagai transformasi dari sumber, jadi perubahan menjalar otomatis dan tidak ada field sensitif yang tertinggal tanpa sengaja.
Contoh: User Publik vs User Internal
interface UserInternal {
id: string;
username: string;
email: string;
passwordHash: string;
role: "admin" | "member";
createdAt: Date;
}
// API publik: buang yang sensitif
type UserPublik = Omit<UserInternal, "passwordHash" | "email">;
// Kartu daftar: hanya yang ditampilkan di UI
type KartuUser = Pick<UserInternal, "id" | "username" | "role">;
// Input registrasi: id dan createdAt dibuat server
type UserBaru = Omit<UserInternal, "id" | "createdAt" | "passwordHash">;
function kePublik(u: UserInternal): UserPublik {
const { passwordHash, email, ...rest } = u;
return rest;
}
const kartu: KartuUser = {
id: "u1",
username: "budi_dev",
role: "member",
}; // OK, hanya tiga fieldAturan praktisnya sederhana: sedikit yang dibutuhkan, pakai Pick; sedikit yang dibuang, pakai Omit. Dari sepuluh field dan hanya dua yang ditampilkan, Pick lebih jelas karena yang tertulis hanyalah yang dipakai. Dari sepuluh field dan hanya satu yang disembunyikan, Omit lebih ringkas. Tambah field avatarUrl di UserInternal, dan UserBaru serta UserPublik otomatis mengetahuinya tanpa ada yang perlu diingat.
Error Nyata: Key yang Tidak Ada di T
Pick dan Omit menolak key yang tidak ada di tipe sumber. Ini bukan cerewet, ini fitur keamanan:
type Salah = Pick<UserInternal, "id" | "warnaRambut">;
// Error: Type '"id" | "warnaRambut"' does not satisfy the constraint
// 'keyof UserInternal'.
// Type '"warnaRambut"' is not assignable to type 'keyof UserInternal'.Typo nama field langsung ketahuan saat compile, bukan saat user melihat halaman kosong. Skenario yang lebih berharga: kamu me-rename username menjadi handle di UserInternal. Setiap Pick dan Omit yang masih menyebut username langsung error, dan compiler memberimu daftar lengkap semua varian yang harus diperbaiki. Tanpa utility type, varian duplikat itu diam-diam basi dan bugnya baru muncul di production.
Satu jebakan yang perlu diingat: Omit membuang key dari tipe, bukan dari object runtime. Fungsi kePublik di atas tetap harus menghapus fieldnya secara manual dengan destructuring; Omit hanya menjamin tipe return-nya benar. Tipe tidak pernah menyentuh data yang berjalan.
Catatan teknis:
Omit<T, K>sebenarnya didefinisikan sebagaiPick<T, Exclude<keyof T, K>>. ArtinyaOmitmewarisi semua perilakuPick, termasuk validasi key terhadapkeyof T. Memahami komposisi ini membantumu membaca pesan error dengan tepat: errorOmityang menyebut constraint hampir selalu berarti ada key yang salah ketik atau sudah di-rename di tipe sumber.
Memilih dengan Tepat di Project Nyata
Pola yang paling sering muncul: Omit untuk DTO create dan update di mana field buatan server seperti id dan createdAt tidak boleh dikirim client, Pick untuk view model yang hanya butuh sedikit field dari entity besar. Keduanya murni level tipe, hilang setelah compile, dan menjaga satu sumber kebenaran. Setiap kali kamu tergoda menulis interface yang isinya "hampir sama dengan interface lain", berhentilah sejenak: kemungkinan besar Pick atau Omit adalah jawabannya.
Tantangan
Katalog dan Ringkasan Produk
Diberi interface Produk { id: string; nama: string; harga: number; stok: number; deskripsi: string; kodeInternal: string }, buat type KatalogProduk yang membuang kodeInternal (pakai Omit) dan type RingkasanProduk yang hanya berisi id, nama, dan harga (pakai Pick). Buat satu objek untuk tiap type yang lolos type-check.