Masukkan Password
58 Apa itu Generik (Generics)
Materi ini membahas Generics di TypeScript, dan ini termasuk konsep yang sangat penting karena nantinya akan sering kamu temui di project nyata.
Kalau disederhanakan, inti dari Generics adalah:
🔑 Generic = “tipe data yang belum ditentukan sekarang, tetapi akan ditentukan nanti.”
Jadi kita bisa membuat function, class, interface, atau struktur data yang fleksibel untuk berbagai tipe, tetapi tetap mendapatkan type safety.
1. Kenapa kita membutuhkan Generics?
Mari mulai dari masalah yang ada di lecture.
Misalnya kita ingin membuat function swap().
Tujuannya adalah menukar posisi dua elemen dalam array.
Misalnya:
const numbers = [10, 20, 30];
Kita ingin:
index 0 ↔ index 2
sehingga hasilnya:
[30, 20, 10]
Tetapi kita ingin function yang sama juga bisa digunakan untuk:
["A", "B", "C"]
atau:
[true, false, true]
atau bahkan:
[
{ name: "John" },
{ name: "Mark" }
]
Jadi kita punya satu kebutuhan:
swap()
↓
bisa menerima array number
bisa menerima array string
bisa menerima array boolean
bisa menerima array object
dll.
2. Cara yang kurang bagus: any
Kita bisa saja menulis:
function swap(
arr: any[],
index1: number,
index2: number
): any[] {
// ...
return arr;
}
Ini memang fleksibel.
Kita bisa:
swap([1, 2, 3], 0, 2);
atau:
swap(["A", "B", "C"], 0, 2);
Tetapi ada masalah besar:
anymembuat TypeScript kehilangan informasi tentang tipe data.
3. Masalah any
Misalnya:
const result = swap(["A", "B", "C"], 0, 2);
Sebagai manusia, kita tahu:
input → string[]
output → string[]
Jadi seharusnya kita bisa melakukan:
result[0].toUpperCase();
Karena result[0] adalah string.
Tetapi kalau return type function adalah:
any[]
TypeScript tidak benar-benar tahu bahwa elemen array tersebut adalah string.
Akibatnya, kita kehilangan banyak keuntungan dari TypeScript.
4. Kita sebenarnya ingin “menghubungkan” input dan output
Yang kita inginkan adalah:
Kalau input:
number[]
maka output:
number[]
Kalau:
input:
string[]
maka output:
string[]
Kalau:
input:
boolean[]
maka output:
boolean[]
Dengan kata lain:
Tipe output harus mengikuti tipe input.
Nah, inilah masalah yang bisa diselesaikan dengan Generics.
5. Apa itu Generic?
Misalnya kita menulis:
function swap<T>(arr: T[]): T[] {
// ...
}
Di sini ada:
<T>
T disebut type parameter.
Kamu bisa menganggap T sebagai placeholder untuk tipe data.
Jadi kita belum mengatakan:
T = number
atau:
T = string
Kita hanya mengatakan:
“Nanti ketika function digunakan, tentukan sendiri
Tberdasarkan data yang diberikan.”
6. Analogi paling gampang
Bayangkan kita membuat kotak:
┌─────────────┐
│ T │
└─────────────┘
T adalah label sementara.
Kalau kita masukkan angka:
┌─────────────┐
│ number │
└─────────────┘
maka:
T = number
Kalau kita memasukkan string:
┌─────────────┐
│ string │
└─────────────┘
maka:
T = string
Jadi:
Generic bukan tipe data tertentu. Generic adalah tempat untuk tipe data yang akan ditentukan kemudian.
7. Membongkar syntax Generic
Perhatikan:
function swap<T>(arr: T[]): T[] {
// ...
}
Kita pecah:
function swap<T>(arr: T[]): T[]
↑ ↑ ↑
│ │ │
type parameter │ return type
│
array of T
<T>
Mendeklarasikan generic type parameter.
arr: T[]
Array yang berisi elemen bertipe T.
: T[]
Function mengembalikan array yang juga berisi T.
8. Ketika dipanggil dengan number
Misalnya:
const result = swap([10, 20, 30], 0, 2);
TypeScript melihat:
[10, 20, 30]
adalah:
number[]
Maka TypeScript menyimpulkan:
T = number
Sehingga secara konsep function menjadi:
function swap(
arr: number[],
index1: number,
index2: number
): number[] {
...
}
Jadi result adalah:
number[]
9. Kalau dipanggil dengan string?
Misalnya:
const result = swap(["A", "B", "C"], 0, 2);
TypeScript melihat:
["A", "B", "C"]
adalah:
string[]
Maka:
T = string
Secara konsep:
function swap(
arr: string[],
index1: number,
index2: number
): string[] {
...
}
Maka:
result
bertipe:
string[]
Dan kita bisa melakukan:
result[0].toUpperCase();
TypeScript tahu bahwa result[0] adalah string.
10. Kalau Boolean?
Misalnya:
const result = swap([true, false, true], 0, 1);
TypeScript menentukan:
T = boolean
Maka:
input → boolean[]
output → boolean[]
11. Jadi satu function bisa dipakai untuk banyak tipe
Inilah kekuatan Generic.
Satu function:
function swap<T>(arr: T[]): T[] {
...
}
bisa bekerja dengan:
number[]
string[]
boolean[]
object[]
User[]
Product[]
dan lain-lain
Tanpa harus membuat:
swapNumber()
swapString()
swapBoolean()
swapUser()
12. Generic vs any
Ini perbedaan yang sangat penting.
Menggunakan any
function swap(arr: any[]): any[] {
...
}
Artinya:
“Saya tidak peduli tipe datanya.”
TypeScript kehilangan informasi.
Menggunakan Generic
function swap<T>(arr: T[]): T[] {
...
}
Artinya:
“Saya belum tahu tipe datanya, tetapi tipe input dan output harus saling mengikuti.”
Ini jauh lebih type-safe.
13. Analogi any vs Generic
Bayangkan ada seseorang membawa sebuah kotak.
any
Kamu bilang:
“Isi kotaknya apa pun, saya tidak peduli.”
any
↓
bebas
↓
informasi tipe hilang
Generic
Kamu bilang:
“Saya belum tahu isinya apa. Tapi apa pun isinya, saya akan mempertahankan jenisnya.”
T
↓
number → number
string → string
User → User
Product → Product
Ini inti Generic.
14. Generic tidak hanya menggunakan T
Kamu sering melihat:
<T>
Tetapi sebenarnya nama T bukan keyword.
Kita bisa menulis:
function swap<Type>(arr: Type[]): Type[] {
// ...
}
atau:
function swap<Item>(arr: Item[]): Item[] {
// ...
}
atau:
function swap<Data>(arr: Data[]): Data[] {
// ...
}
Semua valid.
Namun T adalah konvensi yang sangat umum.
Biasanya:
T → Type
K → Key
V → Value
E → Element
Jadi jangan menganggap T adalah syntax khusus. Yang penting adalah konsistensinya.
15. Generic dengan lebih dari satu Type Parameter
Kita juga bisa mempunyai beberapa generic.
Misalnya:
function pair<T, U>(first: T, second: U) {
return [first, second];
}
Sekarang:
const result = pair("John", 30);
TypeScript menentukan:
T = string
U = number
Jadi hasilnya kira-kira:
[string, number]
Kalau:
const result = pair(true, "Hello");
maka:
T = boolean
U = string
16. Built-in Generic di TypeScript
Lecture kemudian masuk ke bagian penting:
TypeScript sendiri banyak menggunakan Generic.
Contohnya:
ArrayPromise- Object-related generic types
Mari kita bahas.
17. Array<T> adalah Generic
Biasanya kita menulis:
const numbers: number[] = [10, 20, 30];
Tetapi kita juga bisa menulis:
const numbers: Array<number> = [10, 20, 30];
Keduanya pada dasarnya menyatakan:
array yang berisi number
Jadi:
number[]
kurang lebih setara dengan:
Array<number>
18. Kenapa Array<number> disebut Generic?
Karena Array sendiri bisa menerima berbagai tipe.
Secara konsep:
Array<T>
T adalah placeholder.
Kalau:
Array<number>
berarti:
T = number
Kalau:
Array<string>
berarti:
T = string
Kalau:
Array<boolean>
berarti:
T = boolean
Kalau:
Array<User>
berarti:
T = User
19. Visualisasi Array<T>
Array<T>
│
├── T = number
│ ↓
│ Array<number>
│
├── T = string
│ ↓
│ Array<string>
│
├── T = boolean
│ ↓
│ Array<boolean>
│
└── T = User
↓
Array<User>
Jadi Array adalah semacam template.
20. Generic pada Promise
Promise juga menggunakan Generic.
Misalnya:
const promise = new Promise<number>((resolve, reject) => {
resolve(100);
});
Perhatikan:
Promise<number>
Artinya:
Promise ini nantinya akan menghasilkan
number.
Jadi:
resolve(100);
valid karena:
100 → number
21. Kalau Promise harus menghasilkan string?
Kita bisa:
const promise = new Promise<string>((resolve, reject) => {
resolve("Hello");
});
Sekarang:
Promise<string>
berarti Promise menghasilkan string.
Kalau kita mencoba:
resolve(100);
TypeScript akan memberikan error.
Karena:
Promise<string>
↓
harus resolve string
↓
100 ❌
22. Promise dengan boolean
Bisa juga:
const promise = new Promise<boolean>((resolve, reject) => {
resolve(true);
});
Sekarang:
Promise<boolean>
berarti hasil akhirnya harus boolean.
23. Kenapa Promise perlu Generic?
Karena Promise bisa menghasilkan banyak jenis data.
Misalnya API:
GET /users
↓
Promise<User[]>
API lain:
GET /product
↓
Promise<Product>
API lain:
GET /status
↓
Promise<string>
Jadi Generic membuat Promise bisa tetap fleksibel sekaligus type-safe.
24. Generic pada API sangat sering digunakan
Contoh:
async function getUsers(): Promise<User[]> {
// ...
}
Artinya:
function
↓
Promise
↓
User[]
Jadi ketika function selesai:
const users = await getUsers();
TypeScript tahu:
users: User[]
Ini sangat berguna dalam aplikasi nyata.
25. Object dan Generic
Lecture juga menyebut object sebagai generic.
Secara konsep, JavaScript object dapat mempunyai property dengan key yang dinamis:
const user = {
name: "John",
age: 30
};
Key-nya:
name
age
dan value-nya bisa mempunyai tipe berbeda.
Namun ada sedikit nuansa penting di sini.
Kalau lecture mengatakan:
“Object adalah generic.”
Maksud sederhananya adalah object dalam TypeScript bisa dimodelkan dengan tipe generik seperti:
Record<string, T>
Misalnya:
const scores: Record<string, number> = {
John: 90,
Mark: 85,
Alice: 95
};
Record sendiri adalah built-in generic utility type.
26. Record<K, T>
Ini bahkan contoh generic yang sangat bagus.
Record<string, number>
artinya:
key → string
value → number
Contohnya:
const scores: Record<string, number> = {
John: 90,
Mark: 85,
Alice: 95
};
Kita tidak tahu nama key-nya dari awal.
Tetapi kita tahu:
key harus string
value harus number
27. Jadi built-in Generic Type yang sering kamu temui
Beberapa yang sangat penting:
Array<T>
Promise<T>
Record<K, T>
Contoh:
Array<string>
→ array berisi string.
Promise<User>
→ Promise menghasilkan User.
Record<string, number>
→ object dengan key string dan value number.
28. Generic berbeda dengan Union
Ini juga harus dibedakan.
Union
string | number
Artinya:
Value bisa string atau number.
Contoh:
let value: string | number;
value = "Hello";
value = 100;
Generic
T
Artinya:
Tipe akan ditentukan kemudian.
Misalnya:
function identity<T>(value: T): T {
return value;
}
Kalau:
identity("Hello");
maka:
T = string
Kalau:
identity(100);
maka:
T = number
Jadi:
Union
→ beberapa tipe sudah ditentukan
Generic
→ tipe belum ditentukan, nanti akan diisi
29. Contoh Generic paling sederhana: identity
Ini contoh klasik Generic.
function identity<T>(value: T): T {
return value;
}
Kalau:
const result = identity("Hello");
maka:
T = string
sehingga:
result: string
Kalau:
const result = identity(100);
maka:
T = number
sehingga:
result: number
Function-nya sama.
Tidak perlu:
identityString()
identityNumber()
30. Generic mempertahankan hubungan tipe
Ini sebenarnya inti paling dalam dari Generic.
Lihat:
function identity<T>(value: T): T
Ada T di dua tempat:
input output
↓ ↓
T → T
Artinya:
Tipe yang masuk harus menjadi tipe yang keluar.
Kalau:
string masuk
↓
string keluar
Kalau:
number masuk
↓
number keluar
Kalau:
User masuk
↓
User keluar
31. Ini berbeda dari any
Bandingkan:
function identity(value: any): any {
return value;
}
dengan:
function identity<T>(value: T): T {
return value;
}
any:
input → any
output → any
Tidak ada hubungan yang jelas.
Generic:
input → T
↓
output → T
Hubungannya dipertahankan.
32. Generic bisa digunakan pada object
Misalnya:
interface Box<T> {
value: T;
}
Sekarang kita bisa membuat:
const numberBox: Box<number> = {
value: 100
};
dan:
const stringBox: Box<string> = {
value: "Hello"
};
Interface-nya sama:
Box<T>
tetapi tipe T bisa berbeda.
33. Visualisasi Box<T>
Box<T>
│
┌───────┴───────┐
│ │
Box<number> Box<string>
│ │
value: 100 value: "Hello"
Jadi Generic membuat kita bisa membuat template tipe yang reusable.
34. Generic sebenarnya sangat dekat dengan konsep “template”
Cara paling mudah membayangkannya:
Generic
↓
Template
↓
Belum tahu tipe spesifik
↓
Nanti diisi
↓
Bisa digunakan berkali-kali
Misalnya:
Box<T>
adalah template.
Kemudian:
Box<number>
adalah versi untuk number.
Dan:
Box<string>
adalah versi untuk string.
35. Apakah Generic ada di JavaScript?
Tidak.
Ini juga ditekankan di lecture.
Generic adalah fitur TypeScript.
Ketika TypeScript dikompilasi menjadi JavaScript, syntax Generic akan hilang.
Misalnya TypeScript:
function identity<T>(value: T): T {
return value;
}
akan menjadi JavaScript kira-kira:
function identity(value) {
return value;
}
Bagian:
<T>
hilang.
36. Kenapa tetap berguna kalau hilang?
Karena Generic bekerja terutama pada compile time.
Saat kita menulis TypeScript:
function identity<T>(value: T): T {
return value;
}
TypeScript melakukan pengecekan.
Misalnya:
const result = identity("Hello");
TypeScript tahu:
T = string
result = string
Informasi tersebut membantu kita selama development.
Setelah dikompilasi:
TypeScript
↓
Type checking
↓
JavaScript
Generic tidak perlu ada di JavaScript karena JavaScript memang tidak memiliki sistem type seperti TypeScript.
37. Generic bukan runtime feature
Ini penting.
Generic tidak melakukan transformasi data.
Misalnya:
identity<number>("Hello")
akan ditolak oleh TypeScript karena:
T = number
input harus number
"Hello" = string
Tetapi Generic sendiri tidak melakukan konversi:
"Hello"
↓
number
Tidak ada conversion.
Generic hanya membantu TypeScript memahami tipe.
38. Generic + Type Safety
Sekarang kita bisa melihat tujuan besarnya:
GENERICS
│
┌──────────┴──────────┐
│ │
Flexible Reusable
│ │
└──────────┬──────────┘
↓
Type Safety
Kita ingin:
Kode yang fleksibel, tetapi tetap aman secara tipe.
Kalau menggunakan any, kita mendapatkan:
Flexible ✅
Type Safety ❌
Kalau menggunakan Generic:
Flexible ✅
Type Safety ✅
Itulah alasan Generic sangat penting.
39. Kapan kita sebaiknya menggunakan Generic?
Gunakan Generic ketika:
1. Kita ingin function bekerja dengan banyak tipe
function identity<T>(value: T): T
2. Tipe output bergantung pada tipe input
function first<T>(arr: T[]): T
Misalnya:
first([1, 2, 3])
→ number
Sedangkan:
first(["A", "B"])
→ string
3. Kita ingin membuat struktur data reusable
interface Box<T> {
value: T;
}
4. Kita ingin membuat API/helper yang type-safe
Contohnya:
Promise<User[]>
atau:
Record<string, number>
40. Hubungkan dengan materi sebelumnya
Generics sebenarnya merupakan kelanjutan yang bagus dari materi-materi sebelumnya.
Kita sudah belajar:
Union
string | number
→ beberapa tipe yang sudah ditentukan.
Type Guard
typeof value === "string"
→ mengecek tipe.
Function Overloading
function fn(string): string;
function fn(number): number;
→ satu function punya beberapa signature.
Generic
function fn<T>(value: T): T
→ satu function bisa digunakan untuk banyak tipe tanpa kehilangan hubungan tipe.
41. Perbedaan any, Union, dan Generic
Ini bagus untuk kamu hafalkan:
| Konsep | Makna |
|---|---|
any | “Tipe apa pun, saya tidak peduli” |
string | number | “Bisa string atau number” |
T | “Tipe belum ditentukan, nanti akan ditentukan” |
T[] | “Array yang elemennya bertipe T” |
Promise<T> | “Promise yang menghasilkan T” |
Array<T> | “Array yang berisi T” |
42. Contoh lengkap Generic Function
Mari kita buat function yang mengambil elemen pertama:
function first<T>(arr: T[]): T {
return arr[0];
}
Sekarang:
const number = first([10, 20, 30]);
TypeScript:
T = number
Maka:
number: number
Kalau:
const name = first(["John", "Mark"]);
TypeScript:
T = string
Maka:
name: string
Satu function, berbagai tipe.
43. Bahkan bisa digunakan dengan object
interface User {
name: string;
age: number;
}
const user = first<User>([
{
name: "John",
age: 30
},
{
name: "Mark",
age: 25
}
]);
Sekarang:
user.name
diketahui sebagai:
string
dan:
user.age
diketahui sebagai:
number
44. Generic Type bisa ditulis eksplisit atau diinfer
Ada dua cara.
Cara 1 — TypeScript menebak
const result = first([10, 20, 30]);
TypeScript otomatis tahu:
T = number
Ini disebut type inference.
Cara 2 — Kita tentukan sendiri
const result = first<number>([10, 20, 30]);
Kita secara eksplisit mengatakan:
T = number
Biasanya TypeScript cukup pintar sehingga kita tidak perlu menuliskannya jika bisa diinfer dengan jelas.
45. Contoh dengan Promise
Kita juga bisa menentukan:
const promise = new Promise<number>((resolve) => {
resolve(100);
});
Di sini:
Promise<T>
↓
T = number
Maka:
Promise<number>
harus menghasilkan number.
Kalau:
resolve("Hello");
❌ Error.
Karena kita sudah mengatakan:
Promise<number>
46. Satu koreksi kecil terhadap lecture
Lecture menyebut:
“array, promise, dan objects adalah built-in generics.”
Untuk pemahaman awal, cara tersebut boleh dipakai.
Tetapi secara terminologi TypeScript yang lebih tepat:
Array<T>adalah generic interface/type bawaan.Promise<T>adalah generic interface bawaan.- Untuk object dengan key/value dinamis, kita sering menggunakan index signature atau utility type generic seperti
Record<K, T>.
Jadi lebih tepat kalau kamu mengingat:
Array<T>
Promise<T>
Record<K, T>
sebagai contoh struktur/type generic bawaan yang sering digunakan.
47. Kesimpulan besar
Mari kita sederhanakan seluruh lecture.
Misalnya kita mempunyai:
function swap<T>(arr: T[]): T[] {
// ...
}
T adalah placeholder tipe.
Ketika dipakai:
swap([1, 2, 3])
maka:
T = number
sehingga:
number[] → number[]
Ketika:
swap(["A", "B", "C"])
maka:
T = string
sehingga:
string[] → string[]
Ketika:
swap([true, false])
maka:
T = boolean
sehingga:
boolean[] → boolean[]
Jadi Generic memungkinkan kita membuat:
satu kode yang fleksibel untuk banyak tipe, tetapi tetap mempertahankan informasi tipe dan type safety.
🧠 Cheat Sheet Generics
Generic function
function identity<T>(value: T): T {
return value;
}
T = placeholder tipe
Generic array
function first<T>(arr: T[]): T {
return arr[0];
}
number[] → number
string[] → string
User[] → User
Generic interface
interface Box<T> {
value: T;
}
Box<number>
Box<string>
Box<User>
Built-in generic
Array<number>
Array<string>
Promise<number>
Promise<User>
Record<string, number>
any vs Generic
any
↓
"Bebas, saya tidak peduli tipe"
↓
type safety berkurang
Generic<T>
↓
"Saya belum tahu tipenya"
↓
"Nanti tentukan"
↓
"Setelah ditentukan, pertahankan tipe tersebut"
↓
type safety tetap terjaga
🔑 Kalimat yang paling penting
Generic adalah placeholder untuk tipe data yang memungkinkan kita membuat kode yang reusable dan fleksibel tanpa kehilangan type safety.
Atau dengan bahasa super sederhana:
Titu seperti kotak kosong untuk tipe data. Kita belum tahu kotaknya berisinumber,string,User, atau tipe lain. Nanti TypeScript akan mengisinya berdasarkan cara kita menggunakan kode tersebut.