Setelah kita memahami apa itu TypeScript dan bagaimana memigrasikan proyek JavaScript ke dalamnya, langkah selanjutnya adalah mendalami salah satu fitur paling canggih dan powerful dari TypeScript: Utility Types. Fitur ini memungkinkan kita untuk memanipulasi, mentransformasi, atau menyaring tipe data yang sudah ada, tanpa perlu mendefinisikan ulang semuanya dari awal.
Utility types sangat berguna untuk meningkatkan fleksibilitas dan reusability tipe data Kamu, membuat kode lebih ringkas, aman, dan mudah di-maintenance. Mari kita jelajahi beberapa utility types dasar yang paling sering digunakan.
Mengapa Utility Types Penting?
Bayangkan Kamu memiliki sebuah tipe data yang kompleks untuk sebuah objek. Namun, dalam skenario yang berbeda, Kamu mungkin hanya memerlukan sebagian dari propertinya, atau semua propertinya harus bersifat opsional atau bahkan Kamu perlu membuat objek baru di mana kunci dan nilainya berasal dari tipe yang sudah ada. Tanpa utility types, Kamu mungkin harus menulis ulang definisi tipe yang baru berulang kali, yang bisa jadi membosankan dan rentan kesalahan.
Utility types hadir untuk menyelesaikan masalah ini. Mereka menyediakan fungsi bawaan untuk memanipulasi tipe data secara langsung, mirip dengan bagaimana fungsi pada nilai variabel bekerja. Ini membuat definisi tipe Kamu lebih DRY (Don’t Repeat Yourself) dan lebih mudah dikelola.
Utility Types Dasar yang Wajib Diketahui
Berikut adalah beberapa utility types yang akan sangat membantu dalam pengembangan Kamu, lengkap dengan penjelasan dan contoh kode yang menunjukkan penggunaan yang benar dan kesalahan yang harus dihindari:

Partial
Partial<Type>
akan mengambil semua properti dari Type
yang diberikan dan menjadikannya opsional. Ini berarti semua properti dari tipe yang dihasilkan bisa ada atau tidak ada.
- Sintaks:
Partial<T>
- Kapan Digunakan:
- Saat membuat fungsi untuk memperbarui objek di mana Kamu tidak mengharapkan semua properti disediakan, hanya yang ingin diubah.
- Ketika mendefinisikan objek yang hanya memiliki sebagian properti dari sebuah tipe lengkap.
Contoh:
interface UserProfile {
id: number;
name: string;
email: string;
phone?: string; // Properti opsional bawaan
}
// Semua properti di UpdateUserProfile menjadi opsional
type UpdateUserProfile = Partial<UserProfile>;
// --- Penggunaan yang Benar ---
const existingUser: UserProfile = {
id: 1,
name: "John Doe",
email: "[email protected]",
};
const userUpdatePartial: UpdateUserProfile = {
name: "Jane Doe",
phone: "123-456-7890"
};
const userUpdateEmpty: UpdateUserProfile = {}; // Objek kosong juga valid
// --- Kesalahan Umum / Batasan ---
// const invalidUserUpdate: UpdateUserProfile = {
// age: 30 // Error: Properti 'age' tidak ada dalam tipe 'Partial<UserProfile>'.
// };
// Keterangan: Kamu tidak bisa menambahkan properti yang tidak ada di tipe aslinya.
console.log("Update Partial:", userUpdatePartial);
console.log("Update Empty:", userUpdateEmpty);
Readonly
Readonly<Type>
mengambil semua properti dari Type
dan menjadikannya hanya-baca (read-only). Ini berarti setelah sebuah objek dengan tipe Readonly<Type>
dibuat, properti-propertinya tidak dapat dimodifikasi.
- Sintaks:
Readonly<T>
- Kapan Digunakan:
- Untuk memastikan imutabilitas data, terutama ketika objek dilewatkan antar fungsi atau komponen dan Kamu tidak ingin objek tersebut dimodifikasi secara tidak sengaja (misalnya, state aplikasi yang tidak boleh diubah langsung).
- Mendefinisikan konfigurasi yang tidak dapat diubah setelah inisialisasi.
Contoh:
interface Product {
sku: string;
name: string;
price: number;
}
// Semua properti di ImmutableProduct menjadi read-only
type ImmutableProduct = Readonly<Product>;
// --- Penggunaan yang Benar ---
const myProduct: ImmutableProduct = {
sku: "P001",
name: "Laptop Pro",
price: 1500
};
// Kita bisa membaca properti
console.log("Product Name:", myProduct.name); // Output: Laptop Pro
// --- Kesalahan Umum / Batasan ---
// myProduct.price = 1600; // Error: Tidak bisa menetapkan ke 'price' karena properti ini hanya-baca.
// myProduct.name = "Desktop Ultra"; // Error: Tidak bisa menetapkan ke 'name' karena properti ini hanya-baca.
// Perhatikan Batasan Readonly pada Properti Bertipe Objek:
interface Config {
version: string;
settings: {
theme: string;
};
}
type ReadonlyConfig = Readonly<Config>;
const appConfig: ReadonlyConfig = {
version: "1.0.0",
settings: {
theme: "dark"
}
};
// appConfig.version = "1.0.1"; // Error: Tidak bisa menetapkan ke 'version'.
// appConfig.settings.theme = "light"; // Ini BOLEH! Readonly hanya berlaku pada level properti pertama.
// Keterangan: Readonly hanya membuat properti di level pertama menjadi hanya-baca. Jika ada properti yang nilainya adalah objek, properti objek tersebut masih bisa diubah. Untuk imutabilitas rekursif, kita perlu solusi kustom atau library seperti 'immer'.
console.log("Theme after modification (Readonly):", appConfig.settings.theme); // Output: light

Pick
Pick<Type, Keys>
membangun tipe objek baru dengan memilih properti tertentu dari Type
yang diberikan. Keys
harus berupa literal string (atau union of string literals) yang ada di Type
.
- Sintaks:
Pick<T, K extends keyof T>
- Kapan Digunakan:
- Ketika Kamu hanya membutuhkan subset properti dari tipe yang lebih besar untuk sebuah fungsi atau komponen.
- Membuat tipe
DTO (Data Transfer Object)
yang lebih kecil dari model database penuh.
Contoh:
interface UserDetails {
id: number;
username: string;
email: string;
isActive: boolean;
lastLogin: Date;
}
// Memilih hanya 'id' dan 'username' dari UserDetails
type UserLoginInfo = Pick<UserDetails, "id" | "username">;
// --- Penggunaan yang Benar ---
const loginInfo: UserLoginInfo = {
id: 101,
username: "dev_koding"
};
// --- Kesalahan Umum / Batasan ---
// const invalidLoginInfo1: UserLoginInfo = {
// id: 102,
// username: "tester",
// email: "[email protected]" // Error: Objek literal hanya boleh menentukan properti yang dikenal, dan 'email' tidak ada dalam tipe 'UserLoginInfo'.
// };
// Keterangan: Tipe yang dihasilkan oleh Pick hanya akan memiliki properti yang Kamu pilih.
// const invalidLoginInfo2: UserLoginInfo = {
// id: 103 // Error: Properti 'username' hilang dalam tipe '{ id: number; }' tetapi diperlukan dalam tipe 'UserLoginInfo'.
// };
// Keterangan: Properti yang Kamu pilih melalui Pick akan tetap menjadi properti yang wajib ada, kecuali Kamu gabungkan dengan Partial.
// const pickNonExistent: Pick<UserDetails, "id" | "password">; // Error: Tipe '"password"' tidak dapat ditetapkan ke tipe '"id" | "username" | "email" | "isActive" | "lastLogin"'.
// Keterangan: Kamu hanya bisa memilih properti yang benar-benar ada di tipe `Type` asli.
console.log("User Login Info:", loginInfo);
Omit
Omit<Type, Keys>
membangun tipe baru dengan mengambil semua properti dari Type
kecuali properti tertentu yang disebutkan di Keys
. Ini adalah kebalikan fungsional dari Pick
.
- Sintaks:
Omit<T, K extends keyof any>
- Kapan Digunakan:
- Ketika Kamu memiliki tipe besar dan ingin mengecualikan beberapa properti yang bersifat sensitif atau tidak relevan untuk konteks tertentu (misalnya, menghapus properti ID internal atau password hash saat mengirim data ke klien).
- Membuat varian tipe yang sedikit berbeda dari yang asli.
Contoh:
interface FullCustomer {
customerId: string;
name: string;
email: string;
creditCardInfo: string; // Properti sensitif
shippingAddress: string;
}
// Membuat tipe CustomerView dengan menghilangkan 'creditCardInfo'
type CustomerView = Omit<FullCustomer, "creditCardInfo">;
// --- Penggunaan yang Benar ---
const customerData: CustomerView = {
customerId: "CUST001",
name: "Budi Santoso",
email: "[email protected]",
shippingAddress: "Jl. Merdeka No. 10"
};
// --- Kesalahan Umum / Batasan ---
// const invalidCustomerData: CustomerView = {
// customerId: "CUST002",
// name: "Ani",
// email: "[email protected]",
// creditCardInfo: "1234-5678-9012-3456", // Error: Objek literal hanya boleh menentukan properti yang dikenal, dan 'creditCardInfo' tidak ada dalam tipe 'CustomerView'.
// shippingAddress: "Jl. Damai No. 5"
// };
// Keterangan: Properti yang di-omit tidak boleh ada dalam objek yang dibuat dengan tipe yang dihasilkan.
// const omitNonExistent: Omit<FullCustomer, "nonExistentProp">; // Ini Boleh, tidak menyebabkan error
// Keterangan: Jika Kamu mencoba meng-omit properti yang tidak ada di tipe asli, TypeScript akan mengabaikannya dan tidak akan ada error.
console.log("Customer Data:", customerData);
Record
Record<Keys, Type>
membangun tipe objek di mana properti kuncinya (Keys) berasal dari string
, number
, atau symbol
yang diberikan, dan nilai-nilai propertinya memiliki Type
yang diberikan.
- Sintaks:
Record<K extends keyof any, T>
- Kapan Digunakan:
- Ketika Kamu ingin membuat objek yang berfungsi sebagai dictionary atau peta, di mana Kamu tahu tipe kunci dan tipe nilai dari semua properti objek.
- Membuat objek enumerasi dengan tipe nilai tertentu.
Contoh:
type ProductStatus = "inStock" | "outOfStock" | "discontinued";
type ProductCount = number;
// Membuat objek di mana kunci adalah ProductStatus dan nilai adalah ProductCount
type InventoryStatus = Record<ProductStatus, ProductCount>;
// --- Penggunaan yang Benar ---
const currentInventory: InventoryStatus = {
inStock: 500,
outOfStock: 20,
discontinued: 10
};
const emptyRecord: Record<string, any> = {}; // Contoh Record yang lebih umum untuk objek kosong dengan kunci string apa pun dan nilai 'any'
// --- Kesalahan Umum / Batasan ---
// const incompleteInventory: InventoryStatus = {
// inStock: 100,
// outOfStock: 5 // Error: Properti 'discontinued' hilang dalam tipe '{ inStock: number; outOfStock: number; }' tetapi diperlukan dalam tipe 'Record<ProductStatus, ProductCount>'.
// };
// Keterangan: Semua kunci yang didefinisikan dalam 'Keys' harus ada dalam objek yang dibuat.
// const invalidKeyInventory: InventoryStatus = {
// inStock: 100,
// outOfStock: 5,
// discontinued: 10,
// pending: 5 // Error: Objek literal hanya boleh menentukan properti yang dikenal, dan 'pending' tidak ada dalam tipe 'Record<ProductStatus, ProductCount>'.
// };
// Keterangan: Kunci properti harus sesuai dengan 'Keys' yang didefinisikan.
console.log("Current Inventory:", currentInventory);
Exclude
Exclude<UnionType, ExcludedMembers>
membangun tipe baru dengan mengecualikan beberapa anggota dari sebuah union type (tipe gabungan).
- Sintaks:
Exclude<T, U>
- Kapan Digunakan:
- Ketika Kamu memiliki tipe yang merupakan kombinasi dari beberapa tipe lain dan Kamu ingin membuat subsetnya dengan menghilangkan anggota tertentu.
- Menyaring tipe union berdasarkan kondisi.
Contoh:
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
// Membuat tipe SafeHttpMethod dengan mengecualikan 'DELETE' dan 'PATCH'
type SafeHttpMethod = Exclude<HttpMethod, "DELETE" | "PATCH">;
// --- Penggunaan yang Benar ---
const method1: SafeHttpMethod = "GET"; // Valid
const method2: SafeHttpMethod = "POST"; // Valid
const method3: SafeHttpMethod = "PUT"; // Valid
const method4: SafeHttpMethod = "OPTIONS"; // Valid
// --- Kesalahan Umum / Batasan ---
// const invalidMethod1: SafeHttpMethod = "DELETE"; // Error: Tipe '"DELETE"' tidak dapat ditetapkan ke tipe '"GET" | "POST" | "PUT" | "OPTIONS"'.
// const invalidMethod2: SafeHttpMethod = "PATCH"; // Error: Tipe '"PATCH"' tidak dapat ditetapkan ke tipe '"GET" | "POST" | "PUT" | "OPTIONS"'.
// Keterangan: Anggota yang telah dikecualikan tidak dapat digunakan dalam tipe yang dihasilkan.
// const invalidMethod3: SafeHttpMethod = "HEAD"; // Error: Tipe '"HEAD"' tidak dapat ditetapkan ke tipe 'SafeHttpMethod'.
// Keterangan: Kamu hanya bisa mengecualikan anggota yang memang ada di 'UnionType' asli. Jika Kamu mencoba menggunakan nilai yang tidak ada di union asli, itu tetap akan menjadi error.
console.log("Safe Method 1:", method1);
console.log("Safe Method 2:", method2);
Utility types adalah alat yang sangat kuat dalam TypeScript yang memungkinkan Kamu bekerja dengan tipe data secara lebih dinamis, efisien, dan aman. Dengan menguasai Partial
, Readonly
, Pick
, Omit
, Record
, dan Exclude
, Kamu sudah memiliki fondasi yang kuat untuk menulis kode TypeScript yang lebih ekspresif dan robust. Mereka mengurangi duplikasi kode tipe, meningkatkan keterbacaan, dan membantu Kamu membangun sistem yang lebih tangguh.
Teruslah bereksperimen dengan utility types ini dalam proyek Kamu. Kamu akan menemukan bahwa mereka adalah aset tak ternilai dalam membangun aplikasi yang kompleks dan mudah di-maintenance. Selanjutnya, kita akan melangkah lebih jauh ke utility types yang lebih advanced untuk manipulasi tipe yang lebih kompleks.