Generic pada Fungsi
Fungsi yang bekerja untuk tipe apa pun tanpa kehilangan info tipe: generic <T>.
Masalah yang Diselesaikan Generic
Kamu butuh fungsi ambilPertama yang bekerja untuk array string, array number, array object. Tanpa generic, pilihanmu jelek semua: duplikasi fungsi per tipe (capek dan rawan beda perilaku), atau satu fungsi bertipe any (tipe hilang, autocomplete mati, bug lolos). Generic adalah jalan ketiga: tulis sekali, tetap type-safe untuk semua tipe. Analoginya seperti wadah serbaguna: satu cetakan yang bisa mencetak kue rasa apa pun, tapi rasa yang keluar selalu sama dengan adonan yang masuk.
Generic: Tipe sebagai Parameter
Perhatikan perbedaan tajam antara any dan generic lewat fungsi identitas sederhana:
// Versi any: info tipe HILANG
function identitasAny(x: any): any {
return x;
}
const a = identitasAny("halo");
a.toUpperCase(); // OK, tapi...
a.tidakAda(); // juga OK! compiler diam saja. Bahaya.
// Versi generic: info tipe TERJAGA
function identitas<T>(x: T): T {
return x;
}
const b = identitas("halo"); // b: string
b.toUpperCase(); // OK
b.tidakAda();
// Error: Property 'tidakAda' does not exist on type 'string'.T adalah placeholder yang diisi compiler saat fungsi dipanggil. Pada identitas("halo"), compiler meng-infer T = string dari argumen, sehingga return type-nya ikut menjadi string. Inilah inti generic: tipe "mengalir" dari input ke output tanpa kamu tulis manual.
Kapan Dipakai di Project Nyata: API Client
Skenario paling konkret: wrapper fetch yang dipakai seluruh aplikasi. Setiap endpoint mengembalikan bentuk data beda, tapi pola request-nya sama.
async function getJSON<T>(url: string): Promise<T> {
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return (await res.json()) as T;
}
interface User { id: string; nama: string; }
interface Produk { id: string; harga: number; }
const user = await getJSON<User>("/api/user/1");
// user: User, autocomplete nama dan id jalan
const produk = await getJSON<Produk>("/api/produk/9");
// produk: ProdukSatu fungsi melayani puluhan endpoint, dan setiap pemanggilan tetap punya tipe yang presisi. Tanpa generic kamu harus menulis wrapper per endpoint atau mengorbankan type safety.
Generic Ganda dan Inference
Fungsi boleh punya lebih dari satu parameter tipe. Contoh klasik: menggabungkan dua object sambil menjaga tipe keduanya.
function gabung<A, B>(a: A, b: B): A & B {
return { ...a, ...b };
}
const hasil = gabung({ nama: "Budi" }, { umur: 17 });
// hasil: { nama: string } & { umur: number }
console.log(hasil.nama, hasil.umur); // keduanya ter-type dengan benarKamu jarang perlu menulis <A, B> eksplisit karena inference bekerja dari argumen. Tulis eksplisit hanya bila argumennya kosong atau ambigu:
function bungkus<T>(nilai?: T): T[] {
return nilai === undefined ? [] : [nilai];
}
const kosong = bungkus(); // T ter-infer sebagai unknown, kurang berguna
const angka = bungkus<number>(); // eksplisit: number[]Contoh Error Nyata: Generic Bukan Sulap
Pemula sering mengira generic berarti "boleh ngapa-ngapain ke T". Tidak. Di dalam badan fungsi, T masih misterius, jadi compiler melarang akses properti yang belum tentu ada:
function ambilPanjang<T>(x: T): number {
return x.length;
// Error: Property 'length' does not exist on type 'T'.
}Ini bukan compiler yang cerewet, ini compiler yang jujur: kalau T ternyata number, .length memang tidak ada. Solusinya adalah constraint (T extends { length: number }), yang dibahas di modul generic-constraint. Aturan mainnya: generic menjaga tipe yang masuk, bukan memberi kemampuan baru ke tipe tersebut.
Catatan teknis: Konvensi nama parameter generic:
T(type umum),K(key),V(value),E(element). Untuk API publik yang dibaca banyak orang, nama deskriptif sepertiTDataatauTResponseboleh dipakai, tapi jangan berlebihan sampai signature sulit dibaca.
Tantangan
Balik array generik
Buat fungsi generic balik<T>(arr: T[]): T[] yang mengembalikan array baru dengan urutan terbalik (jangan mutasi array asli, pakai spread + reverse). Buktikan tipe terjaga: panggil dengan string[] dan number[].
function balik<T>(arr: T[]): T[] {
// kembalikan array baru terbalik
}
const a = balik(["x", "y", "z"]); // string[]
const b = balik([1, 2, 3]); // number[]
console.log(a, b);