Partial<T> adalah utility type bawaan yang membuat setiap property T menjadi opsional. Ini tipe yang paling pas untuk update atau patch: pemanggil hanya mengirim field yang berubah.
Partial<User> adalah { id?: number; name?: string; email?: string }. Compiler tetap mengecek field yang kamu kirim: { nmae: "x" } atau { name: 42 } adalah error, dan itulah yang membuat Partial lebih baik daripada parameter object atau any yang longgar.
Bagaimana Partial Didefinisikan
Partial adalah mapped type satu baris di standard library TypeScript:
type Partial<T> = {
[P in keyof T]?: T[P];
};
Untuk setiap key P milik T, tipe ini mendeklarasikan property opsional dengan tipe yang sama. Karena melakukan mapping atas keyof T, ia mempertahankan readonly pada property yang memilikinya. Karena hanya tipe biasa, ia tidak berpengaruh saat runtime: object yang dikirim sebagai changes tetap object yang sama.
Membaca dari Partial Menghasilkan T | undefined
Setiap property di Partial<T> mungkin tidak ada, jadi membacanya menghasilkan tipe property itu ditambah undefined. Compiler memaksamu menangani kasus yang kosong:
{ ...defaults, ...opts } adalah cara yang biasa untuk mengubah Partial<Options> kembali menjadi Options yang lengkap: spread nilai default lebih dulu, lalu biarkan nilai yang diberikan menimpanya.
Jebakan undefined Eksplisit
Property opsional boleh tidak ada, tetapi juga boleh hadir dengan nilai undefined. Object spread menyalin undefined itu menimpa nilai aslinya, dan tipe hasilnya tidak menunjukkan hal itu:
Masalah ini muncul saat patch dibangun dari form atau query string yang field kosongnya menjadi undefined. Opsi compiler exactOptionalPropertyTypes (bukan bagian dari strict) membuat { name: undefined } menjadi error kompilasi untuk name?: string, kecuali kamu menulis name?: string | undefined, sehingga masalahnya dicegah sejak awal.
Partial Bersifat Dangkal
Partial hanya membuat property di level teratas menjadi opsional. Object bersarang, kalau kamu mengirimnya, harus lengkap:
index.ts(8,3): error TS2741: Property 'tabSize' is missing in type '{ fontSize: number; }' but required in type '{ fontSize: number; tabSize: number; }'.
Property editor memang opsional, tetapi begitu hadir, tipenya tetap { fontSize: number; tabSize: number }, tidak berubah. Untuk object pengaturan, patch API, dan test fixture, kamu sering menginginkan property opsional di setiap level. Untuk itu dibutuhkan tipe rekursif.
DeepPartial: Partial yang Rekursif
TypeScript tidak punya versi deep bawaan, tetapi cukup beberapa baris untuk membuatnya. Fungsi dan array dibiarkan apa adanya, karena membuat elemen array opsional akan mengizinkan [undefined]:
Tipenya rekursif; penggabungannya tidak. applySettings menggabungkan editor secara manual karena object spread juga dangkal. Fungsi deep merge generic tersedia di library seperti lodash (merge), dan pemberian tipenya lebih sulit daripada tipe di atas.
Required: Kebalikan dari Partial
Required<T> menghapus ? dari setiap property. Tipe ini didefinisikan dengan modifier -?, yang juga menghapus undefined dari tipe setiap property:
Polanya sama seperti Partial, hanya terbalik: pengguna API mengirim konfigurasi yang longgar, lalu kode di dalamnya bekerja dengan versi Required yang semua nilainya dipastikan ada. Spread-nya punya celah yang sama dengan fungsi update di atas: pemanggil yang mengirim port: undefined secara eksplisit menimpa nilai default dengan undefined, dan compiler menerimanya kecuali exactOptionalPropertyTypes aktif. Required bersifat dangkal dengan cara yang sama seperti Partial.
Membuat Hanya Sebagian Property Opsional atau Wajib
Partial dan Required berlaku untuk setiap property. Untuk mengubah beberapa saja, pecah tipenya dengan Pick dan Omit lalu gabungkan lagi:
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
interface Post {
id: number;
title: string;
body?: string;
}
type NewPost = PartialBy<Post, "id">; // id optional, title required, body optional
type Published = RequiredBy<Post, "body">; // body now required
| Tipe | Efek | Deep? |
|---|---|---|
Partial<T> | setiap property opsional | tidak |
Required<T> | setiap property wajib, undefined dihapus | tidak |
DeepPartial<T> (buatanmu sendiri) | opsional di setiap level | ya |
PartialBy<T, K> (buatanmu sendiri) | hanya key K yang opsional | tidak |
Readonly<T> | setiap property readonly | tidak |
Pertanyaan yang sering diajukan
Apa fungsi Partial di TypeScript?
Partial<T> membuat tipe yang berisi semua property T dengan tanda opsional. Untuk interface User { name: string; email: string }, Partial<User> adalah { name?: string; email?: string }, jadi {}, { name: "Ada" }, dan user yang lengkap semuanya nilai yang valid.
Apakah Partial bersifat dalam (deep) di TypeScript?
Tidak, Partial hanya memengaruhi property di level teratas. Object bersarang di dalam Partial<T> tetap harus lengkap. Untuk versi rekursif, tulis tipe DeepPartial<T> yang menerapkan dirinya sendiri ke property bertipe object.
Apa kebalikan dari Partial di TypeScript?
Required<T>. Tipe ini menghapus ? dari setiap property sekaligus menghapus undefined dari tipenya, jadi Required<{ port?: number }> adalah { port: number }. Ia didefinisikan sebagai mapped type dengan modifier -?.
Bagaimana cara membuat hanya sebagian property yang opsional?
Gabungkan Omit, Pick dan Partial: type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>. PartialBy<User, "email"> mempertahankan setiap property seperti semula, kecuali email, yang menjadi opsional.
Kenapa sebuah property menjadi undefined setelah menggabungkan update Partial?
Partial<T> mengizinkan property hadir dengan nilai undefined, dan object spread ikut menyalinnya: { ...user, ...{ name: undefined } } punya name: undefined walaupun TypeScript memberi hasilnya tipe User. Saring nilai undefined sebelum menggabungkan, atau aktifkan exactOptionalPropertyTypes supaya undefined yang eksplisit ditolak.