utility-typesgenericMenengah5 mnt baca

Partial dan Required

Partial membuat semua field opsional untuk update PATCH, Required mengembalikannya menjadi wajib, dan Partial ternyata hanya bekerja di level pertama.

Kenapa Partial Ada?

Bayangkan endpoint PATCH /users/:id di mana client boleh mengirim satu field saja. Tanpa Partial, kamu menulis interface PatchUser yang menyalin semua field User sebagai opsional. Setiap kali User bertambah field, PatchUser harus diingat untuk ikut diupdate, dan manusia pasti lupa. Partial<T> mengubah semua property T menjadi opsional secara otomatis, sehingga varian update selalu sinkron dengan sumbernya. Satu baris, nol duplikasi.

Contoh: Form Update (PATCH)

typescript
interface Pengguna {
  id: string;
  nama: string;
  email: string;
  umur: number;
  bio: string;
}

type PatchPengguna = Partial<Pengguna>;
// { id?: string; nama?: string; email?: string; umur?: number; bio?: string }

async function updatePengguna(id: string, patch: PatchPengguna) {
  const res = await fetch(`/api/users/${id}`, {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(patch),
  });
  return res.json();
}

updatePengguna("u1", { nama: "Budi Baru" }); // OK
updatePengguna("u1", { email: "[email protected]", umur: 17 }); // OK
updatePengguna("u1", {}); // OK juga, tidak ada yang diubah

Template literal di URL fetch hanyalah kode runtime biasa. Yang penting, patch boleh berisi subset apa pun dari field Pengguna, dan kalau besok Pengguna tambah field avatar, PatchPengguna otomatis mendukungnya tanpa disentuh.

Required: Saat Data Harus Lengkap Lagi

Alur umum di backend: terima Partial dari client, validasi, lalu tegaskan sebagai data lengkap sebelum disimpan. Required<T> menghapus semua tanda opsional sehingga setiap property kembali wajib.

typescript
type PenggunaLengkap = Required<Partial<Pengguna>>; // sama dengan Pengguna

const draft: Partial<Pengguna> = { nama: "Budi" };

function simpanKeDb(u: PenggunaLengkap): void {
  // di sini u.email pasti string, bukan string | undefined
}

simpanKeDb(draft);
// Error: Argument of type 'Partial<Pengguna>' is not assignable to
// parameter of type 'Required<Partial<Pengguna>>'.
//   Types of property 'email' are incompatible.
//     Type 'string | undefined' is not assignable to type 'string'.

Error ini persis seperti di project nyata saat lupa memastikan satu field wajib sebelum insert. Compiler menolak data yang mungkin tidak lengkap, dan pesannya menunjuk field yang bermasalah, di sini email.

Jebakan Terkenal: Partial Itu Shallow

Partial hanya menyentuh level pertama. Property nested yang berupa object tetap wajib lengkap bila kamu menyertakannya. Ini benar secara tipe, tapi mengejutkan bila kamu mengira Partial berlaku rekursif ke dalam.

typescript
interface Pengaturan {
  tema: string;
  notifikasi: { email: boolean; push: boolean };
}

const p: Partial<Pengaturan> = { notifikasi: { email: true } };
// Error: Type '{ email: boolean; }' is not assignable to type
// '{ email: boolean; push: boolean; }'.
//   Property 'push' is missing in type '{ email: boolean; }'
//   but required in type '{ email: boolean; push: boolean; }'.

notifikasi boleh absen total karena Partial, tetapi sekali ditulis, isinya harus lengkap. Untuk update nested yang benar-benar parsial, buat level nested-nya Partial juga:

typescript
type PatchPengaturan = Partial<Omit<Pengaturan, "notifikasi">> & {
  notifikasi?: Partial<Pengaturan["notifikasi"]>;
};

const ok: PatchPengaturan = { notifikasi: { email: true } }; // OK sekarang
const jugaOk: PatchPengaturan = { tema: "gelap" }; // OK, notifikasi boleh absen

Pengaturan["notifikasi"] adalah indexed access type: cara mengambil tipe sebuah property tanpa menulis ulang bentuknya.

Catatan teknis: Partial<T> sebenarnya hanyalah mapped type { [P in keyof T]?: T[P] }, dan Required<T> adalah { [P in keyof T]-?: T[P] } di mana tanda minus menghapus opsionalitas. Memahami bentuk aslinya membuka pintu untuk membuat varian sendiri, misalnya DeepPartial rekursif untuk form bertingkat, saat kebutuhanmu melampaui bawaan bahasa.

Kapan Memakai yang Mana?

Pakai Partial untuk input update, draft form yang belum selesai, dan objek opsi. Pakai Required untuk menegaskan data final sebelum disimpan atau dikirim ke sistem yang tidak mentolerir undefined. Keduanya murni level tipe: tanpa runtime cost, dan perubahan di sumber otomatis menjalar ke semua varian.

Tantangan

PatchSetting dengan Nested Parsial

Diberi interface Setting { theme: string; lang: string; alerts: { email: boolean; sms: boolean } }, buat type PatchSetting: semua level pertama opsional, dan alerts bila diberikan boleh sebagian saja. Uji dengan dua objek: { theme: "dark" } dan { alerts: { sms: true } }, keduanya harus lolos type-check tanpa error.