Generic adalah cara menulis satu fungsi (atau satu tipe) yang bisa dipakai untuk banyak tipe tanpa kehilangan informasi tipenya. Caranya dengan type parameter, biasanya bernama T: semacam variabel, tapi isinya tipe, dan ditentukan setiap kali fungsinya dipanggil. Mirip tempat penitipan barang: petugasnya gak peduli kamu titip helm atau payung, tapi yang kamu ambil pasti barang yang sama dengan yang kamu titipkan.
🧪 Tebakan cepat:
terakhir([1, 2, 3])danterakhir(["a", "b"])memanggil fungsi yang sama. Apa tipe hasil masing-masing? Tebak dulu, jawabannya ada di bagian "T ikut argumennya".
Kenapa gak pakai any saja
Kita mau fungsi yang mengambil elemen terakhir dari array apa pun. Cara gampangnya pakai any:
function terakhirAny(arr: any[]): any {
return arr[arr.length - 1];
}
const hasil = terakhirAny([1, 2, 3]); // hasil: any
hasil.toUpperCase(); // TypeScript diam saja
TypeError: hasil.toUpperCase is not a function
any mematikan pengecekan. hasil sebenarnya angka 3, dan angka gak punya .toUpperCase(), tapi TypeScript gak protes. Error-nya baru muncul saat program jalan, padahal TypeScript dipakai justru supaya error seperti ini ketahuan sebelum jalan.
T ikut argumennya
function terakhir<T>(arr: T[]): T {
return arr[arr.length - 1];
}
const a = terakhir([1, 2, 3]); // T = number, a: number, nilainya 3
const b = terakhir(["a", "b"]); // T = string, b: string, nilainya "b"
const c = terakhir([true, false]); // T = boolean, c: boolean, nilainya false
<T>setelah nama fungsi membuat type parameter bernamaT. Kurung siku lancip< >adalah penanda generic. NamaTcuma kebiasaan, dan boleh lebih dari satu, misalnya<K, V>.arr: T[]artinya "array berisi T", dan: Tsesudah kurung artinya fungsinya mengembalikan satu T.- Kamu gak perlu menulis T. TypeScript menyimpulkannya dari argumen. Ini disebut inference.
arr.length - 1adalah index terakhir, karena index dimulai dari 0.[1, 2, 3]punya 3 isi, jadi index terakhirnya 2.
| Panggilan | T | Tipe hasil | Nilai |
|---|---|---|---|
terakhir([1, 2, 3]) |
number |
number |
3 |
terakhir(["a", "b"]) |
string |
string |
"b" |
terakhir([1, "a"]) |
string | number |
string | number |
"a" |
Jadi hasilnya number dan string. Itu jawaban tebakannya. Karena tipenya tepat, b.toUpperCase() boleh, sedangkan a.toUpperCase() ditolak sebelum jalan:
error TS2339: Property 'toUpperCase' does not exist on type 'number'.
T juga boleh kamu tulis sendiri: terakhir<string>(["a", "b"]). Kalau isinya gak cocok, misalnya terakhir<string>([1, 2, 3]), tiap angka ditolak dengan Type 'number' is not assignable to type 'string'. (ts(2322)).
Membatasi T dengan extends
Kadang fungsimu butuh sesuatu dari T. Fungsi panjang di bawah butuh .length:
function panjang<T extends { length: number }>(isi: T): number {
return isi.length;
}
panjang("halo"); // 4
panjang([1, 2, 3]); // 3
panjang(123); // error
error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
T extends { length: number }artinya "T boleh tipe apa saja, asal punyalengthbertipe number". Ini disebut constraint. String dan array punyalength, angka gak punya.- Tanpa
extends, barisreturn isi.lengthsendiri yang error:Property 'length' does not exist on type 'T'.T bisa tipe apa saja, termasuk angka, jadi TypeScript gak bisa menjamin adalength.
Tipe yang generic
Bukan cuma fungsi. Interface dan type alias juga bisa punya type parameter:
interface ApiResponse<T> {
data: T;
status: number;
}
const res: ApiResponse<string[]> = { data: ["Kopi", "Teh"], status: 200 };
Bentuk ApiResponse sama untuk semua endpoint, cuma tipe data-nya yang beda. Kamu sebenarnya sudah sering memakai tipe generic: string[] sama dengan Array<string>.
⚠️ Jebakan yang sering ketemu
- Pakai
anybiar gampang. Kodenya lolos, tapi error pindah ke saat program jalan. Pakai<T>supaya tipe hasilnya tetap tepat. - Lupa
<T>setelah nama fungsi.function terakhir(arr: T[]): Tditolak denganCannot find name 'T'.T harus dideklarasikan dulu di< >. - Index terakhir meleset satu.
arr[arr.length]selaluundefined. Index terakhir adalaharr.length - 1. - Mengembalikan seluruh array.
return arrmengembalikan[1, 2, 3], bukan3, dan TypeScript menolaknya:Type 'T[]' is not assignable to type 'T'.
TL;DR: Generic memakai type parameter seperti <T> supaya satu fungsi bisa dipakai untuk banyak tipe tanpa kehilangan tipenya. function terakhir<T>(arr: T[]): T mengembalikan elemen terakhir (arr[arr.length - 1]), dan T disimpulkan dari argumen: terakhir([1, 2, 3]) bertipe number, terakhir(["a", "b"]) bertipe string. Beda dengan any, kesalahan seperti .toUpperCase() pada angka ketahuan sebelum jalan. Batasi T dengan extends, misalnya T extends { length: number }, supaya panjang(123) ditolak. Interface juga bisa generic, seperti ApiResponse<T>.