Generic pada Interface
Interface dengan parameter tipe: pola ApiResponse<T> yang dipakai di semua project.
Interface yang Bisa Diisi Ulang
Interface biasa itu seperti formulir cetak dengan kolom yang sudah fix. Interface generic itu seperti template formulir: struktur kolomnya sama, tapi jenis isian tiap kolom bisa diganti. Kenapa ini penting? Karena di project nyata, banyak struktur data yang "bungkusnya sama, isinya beda". Contoh paling legendaris: response API.
Pola ApiResponse<T>: Fondasi Semua Project
Hampir setiap backend mengembalikan response dengan bungkus yang konsisten: status sukses, payload data, pesan opsional. Yang berubah hanya isi data. Tanpa generic, kamu menulis interface per endpoint:
// Cara melelahkan: satu interface per endpoint
interface UserResponse { sukses: boolean; data: User; pesan?: string; }
interface ProdukListResponse { sukses: boolean; data: Produk[]; pesan?: string; }
// tambah endpoint, tambah interface... tidak ada habisnyaDengan generic, satu template cukup:
interface ApiResponse<T> {
sukses: boolean;
data: T;
pesan?: string;
}
type UserResponse = ApiResponse<User>;
type ProdukListResponse = ApiResponse<Produk[]>;
type AngkaResponse = ApiResponse<number>;Sekarang fungsi fetch-mu bisa mengembalikan tipe yang presisi tanpa duplikasi:
async function panggil<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
return (await res.json()) as ApiResponse<T>;
}
const hasil = await panggil<User[]>("/api/users");
if (hasil.sukses) {
hasil.data.forEach((u) => console.log(u.nama)); // u: User, aman
}Generic Bisa Lebih dari Satu
Interface boleh punya beberapa parameter tipe, berguna untuk struktur pasangan seperti key-value atau hasil paginasi:
interface Pasangan<K, V> {
kunci: K;
nilai: V;
}
interface Halaman<TItem, TFilter> {
data: TItem[];
total: number;
filterAktif: TFilter;
}
type ProdukPage = Halaman<Produk, { kategori?: string; maxHarga?: number }>;Satu interface Halaman melayani halaman produk, halaman user, halaman transaksi, masing-masing dengan tipe item dan filter sendiri.
Contoh Error Nyata: Lupa Mengisi T
Generic interface tanpa argumen tipe sering jadi sumber bug diam-diam. TypeScript mewajibkan argumennya, dan pesannya cukup jelas:
const r: ApiResponse = { sukses: true, data: "x" };
// Error: Generic type 'ApiResponse<T>' requires 1 type argument(s).Jangan "mengakali" dengan ApiResponse<any> karena itu mematikan seluruh manfaatnya: data jadi any dan autocomplete hilang. Isi dengan tipe yang benar, atau beri default (lihat modul generic-default) bila satu tipe memang dominan. Contoh lain yang sering kejadian: salah mengira posisi parameter saat ada dua:
type Salah = Pasangan<string, number>; // kunci: string, nilai: number, benar
const p: Salah = { kunci: 1, nilai: "x" };
// Error: Type 'number' is not assignable to type 'string'.
// Error: Type 'string' is not assignable to type 'number'.Compiler menangkapnya sebelum runtime. Inilah nilai jualnya: struktur bungkus yang konsisten plus isi yang tetap diperiksa ketat.
Kapan Memakai Interface Generic vs Method Generic
Ada dua gaya, dan pemilihannya penting. Kalau seluruh interface "tentang" satu tipe, jadikan interface-nya generic. Kalau hanya satu-dua method yang butuh fleksibilitas, jadikan method-nya saja yang generic:
// Interface generic: T dipakai di banyak tempat
interface Repositori<T> {
simpan(item: T): void;
semua(): T[];
}
// Method generic: interface-nya fix, method-nya fleksibel
interface Serializer {
serialize<T>(nilai: T): string;
deserialize<T>(teks: string): T;
}Serializer tidak perlu jadi Serializer<T> karena tiap pemanggilan serialize bisa membawa tipe beda. Aturan praktisnya: generic di level interface bila tipenya "melekat" pada object sepanjang hidupnya, di level method bila tipenya berganti tiap pemanggilan.
Catatan teknis: Interface generic mendukung default (
interface ApiResponse<T = unknown>) dan constraint (interface Sorted<T extends Comparable>). Kombinasi ini membuat template tipe yang ekspresif tanpa membebani pemakai yang hanya butuh kasus umum.
Tantangan
Bungkus response API
Buat interface ApiResponse<T> dengan sukses: boolean, data: T, pesan?: string. Lalu buat type ProdukResponse = ApiResponse<{ id: string; harga: number }[]> dan satu object contoh yang sukses dengan 1 produk.
interface ApiResponse<T> {
// tulis di sini
}
type ProdukResponse = ApiResponse<{ id: string; harga: number }[]>;
const res: ProdukResponse = {
// isi di sini
};