Dalam artikel sebelumnya, kita sudah menyelami TypeScript Utility Types Dasar seperti Partial
, Readonly
, Pick
, Omit
, Record
, dan Exclude
. Kamu tentu menyadari betapa kuatnya mereka dalam memanipulasi dan mengoptimalkan tipe data. Kini, saatnya kita melangkah lebih jauh.
TypeScript menawarkan serangkaian utility types lain yang lebih advanced, memungkinkan kita untuk bekerja dengan inferensi tipe, properti wajib, nilai nullable, hingga tipe asynchronous dengan cara yang sangat dinamis. Menguasai utility types ini akan membawa pemahaman TypeScript Kamu ke level berikutnya, memungkinkan Kamu membangun sistem yang jauh lebih fleksibel dan robust.
Mengapa Mempelajari Advanced Utility Types?
Saat proyek tumbuh semakin kompleks, Kamu akan sering menghadapi skenario di mana tipe data tidak bisa didefinisikan secara statis begitu saja. Kamu mungkin perlu:
- Mengubah properti opsional menjadi wajib.
- Menghilangkan nilai
null
atauundefined
dari sebuah tipe. - Mendapatkan tipe kembalian (return type) dari suatu fungsi tanpa memanggilnya.
- Menarik tipe parameter dari sebuah fungsi atau constructor.
- Mengekstrak tipe nilai dari Promise.
Advanced utility types ini dirancang khusus untuk skenario-skenario tersebut, memungkinkan inferensi tipe yang cerdas dan transformasi tipe yang kompleks secara compile-time.
Advanced Utility Types yang Wajib Diketahui
Berikut adalah beberapa utility types lanjutan yang akan sangat berguna dalam pengembangan Kamu, lengkap dengan penjelasan dan contoh kode:
1. Required
Required<Type>
adalah kebalikan dari Partial<Type>
. Ini mengambil semua properti dari Type
yang diberikan dan menjadikannya wajib (non-optional).
- Sintaks:
Required<T>
- Kapan Digunakan:
- Ketika Kamu memiliki tipe dengan properti opsional, tetapi dalam konteks tertentu (misalnya, setelah validasi atau inisialisasi), Kamu ingin memastikan semua properti tersebut ada.
- Menciptakan representasi “lengkap” dari sebuah tipe.
Contoh:
interface UserSettings {
theme?: string;
notificationsEnabled?: boolean;
language?: string;
}
// Semua properti di CompleteUserSettings menjadi wajib
type CompleteUserSettings = Required<UserSettings>;
// --- Penggunaan yang Benar ---
const myFullSettings: CompleteUserSettings = {
theme: "dark",
notificationsEnabled: true,
language: "en"
};
// --- Kesalahan Umum / Batasan ---
// const incompleteSettings: CompleteUserSettings = {
// theme: "light" // Error: Property 'notificationsEnabled' is missing in type '{ theme: string; }' but required in type 'CompleteUserSettings'.
// };
// Keterangan: Setelah menggunakan `Required`, semua properti (bahkan yang sebelumnya opsional) harus disediakan.
console.log("Full Settings:", myFullSettings);
2. NonNullable
NonNullable<Type>
membangun tipe baru dengan mengecualikan null
dan undefined
dari Type
yang diberikan. Ini sangat berguna ketika Kamu berurusan dengan union type yang mungkin memiliki nilai nullable.
- Sintaks:
NonNullable<T>
- Kapan Digunakan:
- Ketika Kamu ingin memastikan bahwa suatu nilai tidak akan pernah
null
atauundefined
setelah melakukan pengecekan (misalnya, di dalam guard clause). - Memurnikan tipe data yang mungkin berasal dari input yang tidak pasti.
- Ketika Kamu ingin memastikan bahwa suatu nilai tidak akan pernah
Contoh:
type MyValue = string | number | null | undefined;
// Tipe NonNullableValue hanya akan menjadi 'string | number'
type NonNullableValue = NonNullable<MyValue>;
// --- Penggunaan yang Benar ---
const validString: NonNullableValue = "hello";
const validNumber: NonNullableValue = 123;
function processValue(value: NonNullableValue) {
console.log("Processing:", value);
}
processValue(validString); // Valid
processValue(validNumber); // Valid
// --- Kesalahan Umum / Batasan ---
// const invalidNull: NonNullableValue = null; // Error: Type 'null' is not assignable to type 'string | number'.
// const invalidUndefined: NonNullableValue = undefined; // Error: Type 'undefined' is not assignable to type 'string | number'.
// Contoh penggunaan di fungsi:
function getUserName(user: { name: string | null }): NonNullable<string> {
// Dengan NonNullable, kita jamin hasilnya bukan null.
// TypeScript akan meminta kita untuk menangani kasus null.
return user.name!; // Menggunakan non-null assertion operator (hati-hati penggunaannya)
// Atau lebih baik:
// if (user.name === null) {
// throw new Error("User name is null");
// }
// return user.name;
}
const user = { name: "Alice" };
console.log("User Name:", getUserName(user));
3. ReturnType
ReturnType<Type>
akan mengekstrak tipe kembalian (return type) dari sebuah fungsi.
- Sintaks:
ReturnType<T extends (...args: any) => any>
- Kapan Digunakan:
- Ketika Kamu ingin mendefinisikan tipe variabel yang akan menampung hasil dari pemanggilan fungsi, tanpa harus menulis ulang tipe hasil fungsi tersebut.
- Berguna untuk membuat tipe data DTO (Data Transfer Object) berdasarkan output fungsi API.
Contoh:
function getUserData(id: number, name: string) {
return { id, name, createdAt: new Date() };
}
async function fetchPost(postId: number) {
const response = await fetch(`https://api.example.com/posts/${postId}`);
return (await response.json()) as { id: number; title: string; body: string };
}
// Mendapatkan tipe kembalian dari fungsi getUserData
type UserData = ReturnType<typeof getUserData>;
// Mendapatkan tipe kembalian dari fungsi fetchPost (setelah Promise diselesaikan)
type PostData = Awaited<ReturnType<typeof fetchPost>>; // Menggunakan Awaited (akan dibahas selanjutnya)
// --- Penggunaan yang Benar ---
const userData: UserData = {
id: 1,
name: "Alice",
createdAt: new Date()
};
const postData: PostData = {
id: 101,
title: "My First Post",
body: "Lorem ipsum..."
};
// --- Kesalahan Umum / Batasan ---
// type InvalidReturnType = ReturnType<string>; // Error: Type 'string' does not satisfy the constraint '(...args: any) => any'.
// Keterangan: ReturnType hanya bisa digunakan pada tipe yang merupakan fungsi.
console.log("User Data Type Example:", userData);
console.log("Post Data Type Example:", postData);
4. Parameters
Parameters<Type>
akan mengekstrak tipe parameter dari sebuah fungsi dalam bentuk tuple type (sebuah array dengan tipe yang ditentukan untuk setiap indeks).
- Sintaks:
Parameters<T extends (...args: any) => any>
- Kapan Digunakan:
- Ketika Kamu ingin membuat fungsi wrapper atau middleware yang menerima parameter yang sama persis dengan fungsi lain.
- Untuk mendefinisikan tipe argumen yang akan dilewatkan ke sebuah fungsi.
Contoh:
function registerUser(username: string, email: string, isActive: boolean) {
console.log(`Registering ${username} with email ${email}. Active: ${isActive}`);
}
// Mendapatkan tipe parameter dari fungsi registerUser
type RegisterUserParams = Parameters<typeof registerUser>;
// --- Penggunaan yang Benar ---
const paramsForUser1: RegisterUserParams = ["john_doe", "[email protected]", true];
function logAndCall(func: (...args: RegisterUserParams) => void, ...args: RegisterUserParams) {
console.log("Calling function with params:", args);
func(...args);
}
logAndCall(registerUser, "jane_doe", "[email protected]", false);
// --- Kesalahan Umum / Batasan ---
// const invalidParams: RegisterUserParams = ["alice"]; // Error: Type '[string]' is not assignable to type '[username: string, email: string, isActive: boolean]'. Source has 1 element(s) but target requires 3.
// Keterangan: Jumlah dan tipe parameter harus sesuai dengan tuple yang dihasilkan.
// type InvalidParamsType = Parameters<number>; // Error: Type 'number' does not satisfy the constraint '(...args: any) => any'.
// Keterangan: Parameters hanya bisa digunakan pada tipe fungsi.
console.log("Parameters for User 1:", paramsForUser1);
5. ConstructorParameters
ConstructorParameters<Type>
akan mengekstrak tipe parameter dari sebuah fungsi constructor kelas dalam bentuk tuple type.
- Sintaks:
ConstructorParameters<T extends new (...args: any) => any>
- Kapan Digunakan:
- Ketika Kamu ingin mendefinisikan tipe argumen yang akan dilewatkan ke constructor sebuah kelas.
- Berguna dalam dependency injection atau factory pattern yang perlu mereplikasi argumen constructor.
Contoh:
class ProductService {
constructor(private apiUrl: string, private apiKey: string) {
// ...
}
fetchProducts() {
// ...
}
}
// Mendapatkan tipe parameter dari constructor ProductService
type ProductServiceCtorParams = ConstructorParameters<typeof ProductService>;
// --- Penggunaan yang Benar ---
const serviceArgs: ProductServiceCtorParams = ["https://api.products.com", "mysecretkey123"];
const myProductService = new ProductService(...serviceArgs);
// --- Kesalahan Umum / Batasan ---
// const invalidCtorArgs: ProductServiceCtorParams = ["only_one_arg"]; // Error: Type '[string]' is not assignable to type '[apiUrl: string, apiKey: string]'.
// Keterangan: Jumlah dan tipe parameter harus sesuai dengan constructor kelas.
// type InvalidCtorType = ConstructorParameters<Function>; // Error: Type 'Function' does not satisfy the constraint 'new (...args: any) => any'.
// Keterangan: ConstructorParameters hanya bisa digunakan pada tipe *constructor* (kelas).
console.log("Product Service Arguments:", serviceArgs);
6. Awaited
Awaited<Type>
adalah utility type yang dirancang untuk mendapatkan tipe dari nilai yang diselesaikan oleh sebuah Promise
. Ini secara rekursif membuka Promise hingga mencapai tipe non-Promise dasar.
- Sintaks:
Awaited<T>
- Kapan Digunakan:
- Ketika Kamu bekerja dengan fungsi asynchronous dan ingin mendapatkan tipe data yang sebenarnya akan dihasilkan setelah
await
dilakukan. - Sangat berguna untuk inferensi tipe yang akurat di lingkungan async/await.
- Ketika Kamu bekerja dengan fungsi asynchronous dan ingin mendapatkan tipe data yang sebenarnya akan dihasilkan setelah
Contoh:
type MyPromiseString = Promise<string>;
type MyNestedPromiseNumber = Promise<Promise<number>>;
type MyDirectValue = boolean;
// Awaited akan "membuka" Promise
type ResolvedString = Awaited<MyPromiseString>; // Hasil: string
type ResolvedNumber = Awaited<MyNestedPromiseNumber>; // Hasil: number
type ResolvedBoolean = Awaited<MyDirectValue>; // Hasil: boolean
// --- Penggunaan yang Benar ---
async function fetchData(): Promise<{ data: string }> {
return Promise.resolve({ data: "Some data" });
}
type DataResult = Awaited<ReturnType<typeof fetchData>>; // Menggabungkan ReturnType dan Awaited
const processedData: DataResult = { data: "Processed data" }; // Sesuai dengan { data: string }
// --- Kesalahan Umum / Batasan ---
// const invalidAwaited: Awaited<string>; // Ini tidak akan menghasilkan error tipe, karena Awaited dirancang untuk mengembalikan tipe aslinya jika bukan Promise.
// Keterangan: Awaited akan bekerja pada tipe non-Promise juga, mengembalikan tipe aslinya. Batasannya adalah jika Kamu mengharapkan Promise tetapi nilai aslinya bukan Promise, mungkin ada kesalahan logika.
console.log("Resolved String Type:", "Hello World" as ResolvedString);
console.log("Resolved Number Type:", 123 as ResolvedNumber);
console.log("Data Result Type:", processedData);
Advanced utility types di TypeScript membuka pintu ke tingkat abstraksi dan keamanan tipe yang lebih tinggi. Dengan menguasai Required
, NonNullable
, ReturnType
, Parameters
, ConstructorParameters
, dan Awaited
, Kamu dapat menulis kode yang lebih ekspresif, lebih aman, dan lebih mudah di-refactor.
Mereka memungkinkan Kamu untuk bekerja dengan inferensi tipe secara cerdas, menangani skenario nullable dengan elegan, dan mengelola tipe fungsi dan constructor dengan presisi. Ini adalah langkah penting untuk menjadi developer TypeScript yang lebih mahir.
Teruslah bereksperimen dengan utility types ini. Selanjutnya, kita akan melangkah lebih jauh lagi dengan mempelajari cara membangun utility types kustom Kamu sendiri, yang akan memberi Kamu kontrol penuh atas manipulasi tipe di TypeScript!