genericfungsiMenengah4 mnt baca

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:

typescript
// 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.

typescript
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: Produk

Satu 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.

typescript
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 benar

Kamu jarang perlu menulis <A, B> eksplisit karena inference bekerja dari argumen. Tulis eksplisit hanya bila argumennya kosong atau ambigu:

typescript
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:

typescript
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 seperti TData atau TResponse boleh 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[].

typescript
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);