Masukkan Password
63 Tipe Generik vs. Tipe Union
Lecture ini membahas satu hal yang sangat penting dan sering membingungkan pemula:
Apa bedanya Generic dengan Union Type (
|)?
Sekilas keduanya memang terlihat mirip karena sama-sama membuat kode bisa menerima beberapa tipe. Tetapi cara kerja dan tujuan keduanya berbeda.
Kalau kamu memahami bagian ini, kamu akan jauh lebih mudah memahami kapan harus menggunakan:
<T>
dan kapan menggunakan:
string | number
1. Masalah utama yang ingin dijelaskan lecture
Kita kembali ke contoh ShoppingCart dari lecture sebelumnya.
Misalnya:
type Book = {
name: string;
pages: number;
price: number;
};
type Cloth = {
name: string;
size: string;
price: number;
};
Kemudian kita membuat Generic Class:
class ShoppingCart<T> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
}
Kita bisa membuat:
const bookCart = new ShoppingCart<Book>();
atau:
const clothCart = new ShoppingCart<Cloth>();
atau:
const stringCart = new ShoppingCart<string>();
atau:
const numberCart = new ShoppingCart<number>();
Nah, muncul pertanyaan:
“Kenapa tidak pakai Union saja?”
Misalnya:
string | number
Bukankah itu juga berarti kita bisa menerima string atau number?
Jawabannya: tidak selalu bisa.
Dan inilah yang menjadi fokus lecture ini.
2. Ingat dulu: apa itu Union Type?
Union Type menggunakan simbol:
|
Contohnya:
let value: string | number;
Artinya:
valueboleh berisistringATAUnumber.
Contoh:
value = "Hello";
Valid.
value = 100;
Juga valid.
Jadi:
string | number
↓
STRING ATAU NUMBER
Union berbicara tentang:
“Nilai ini boleh memiliki salah satu dari beberapa tipe.”
3. Generic berbeda
Generic menggunakan placeholder:
<T>
Contoh:
class ShoppingCart<T> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
}
T artinya:
“Saya belum menentukan tipe sekarang. Nanti tipe saya tentukan ketika class ini digunakan.”
Misalnya:
const cart = new ShoppingCart<string>();
Sekarang:
T = string
Maka cart tersebut menjadi cart khusus string.
Kalau:
const cart = new ShoppingCart<number>();
maka:
T = number
Cart tersebut menjadi cart khusus number.
4. Perbedaan paling penting
Ini inti dari semuanya:
Union
string | number
berarti:
Satu value boleh string atau number.
Generic
<T>
berarti:
Tipe ditentukan kemudian, dan setelah ditentukan akan digunakan secara konsisten.
Ini perbedaan yang sangat penting.
5. Analogi sederhana
Bayangkan ada sebuah kotak.
Union
Kita punya kotak bertuliskan:
┌──────────────────┐
│ String ATAU │
│ Number │
└──────────────────┘
Artinya setiap kali kita memasukkan sesuatu, boleh:
"Hello"
100
"World"
200
Jadi isi kotaknya bisa campur.
Generic
Sekarang kita punya kotak kosong:
┌──────────────────┐
│ T │
└──────────────────┘
Saat membuat kotak, kita menentukan:
T = String
Maka:
┌──────────────────┐
│ String │
│ String │
│ String │
└──────────────────┘
Tidak boleh tiba-tiba memasukkan number.
Kalau kita membuat kotak lain:
T = Number
maka kotak tersebut khusus number.
6. Mari lihat contoh ShoppingCart
Generic version:
class ShoppingCart<T> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
}
Kemudian:
const stringCart = new ShoppingCart<string>();
Berarti:
T = string
Sehingga secara konsep:
items: string[];
addItem(item: string);
getItems(): string[];
Sekarang:
stringCart.addItem("Hello");
stringCart.addItem("World");
✅ Valid.
Tapi:
stringCart.addItem(100);
❌ Error.
Kenapa?
Karena sejak awal kita mengatakan:
new ShoppingCart<string>()
Jadi cart ini khusus string.
7. Bisa membuat cart number juga
Dengan class yang sama:
const numberCart = new ShoppingCart<number>();
Sekarang:
T = number
Maka secara konsep:
items: number[];
addItem(item: number);
getItems(): number[];
Sehingga:
numberCart.addItem(100);
numberCart.addItem(200);
✅ Valid.
Tetapi:
numberCart.addItem("Hello");
❌ Error.
8. Nah, bagaimana kalau menggunakan Union?
Misalnya kita ubah class menjadi:
class ShoppingCart {
private items: string[] | number[] = [];
addItem(item: string | number) {
this.items.push(item);
}
}
Sekilas terlihat seperti:
“Mantap, sekarang cart bisa string dan number.”
Tapi sebenarnya ada masalah.
items kita definisikan sebagai:
string[] | number[]
Artinya:
itemsadalah array string ATAU array number.
Bukan:
(string | number)[]
Ini sangat penting.
9. Bedakan string[] | number[] dengan (string | number)[]
Ini sering bikin bingung.
string[] | number[]
Artinya:
ARRAY STRING
ATAU
ARRAY NUMBER
Contoh valid:
["Hello", "World"]
atau:
[100, 200, 300]
Tetapi bukan:
["Hello", 100]
(string | number)[]
Artinya:
ARRAY
yang setiap elemennya
boleh string ATAU number
Jadi ini valid:
["Hello", 100, "World", 200]
Visualisasi
string[] | number[]
ARRAY A
[string, string, string]
ATAU
ARRAY B
[number, number, number]
Tidak boleh campur.
(string | number)[]
ARRAY
[string, number, string, number]
Boleh campur.
10. Ini inti masalah pada lecture
Lecture mencoba membuat:
items: string[] | number[];
dan:
addItem(item: string | number)
Masalahnya adalah addItem() mengatakan:
“Saya boleh menerima string atau number.”
Contohnya:
cart.addItem("Hello");
cart.addItem(100);
Jadi akhirnya items bisa menjadi:
["Hello", 100]
Padahal kita mengatakan:
string[] | number[]
yang berarti:
array khusus string ATAU array khusus number.
Bukan array campuran.
11. Kalau kita ubah menjadi (string | number)[]?
Kita bisa saja mengatakan:
class ShoppingCart {
private items: (string | number)[] = [];
addItem(item: string | number) {
this.items.push(item);
}
}
Sekarang:
cart.addItem("Hello");
cart.addItem(100);
akan valid.
Array-nya bisa:
["Hello", 100, "World", 200]
Tapi sekarang muncul masalah lain.
Kita sudah kehilangan konsep:
“Satu cart khusus string.”
atau:
“Satu cart khusus number.”
Cart tersebut memang bisa menampung keduanya.
12. Generic menyelesaikan masalah tersebut
Dengan Generic:
class ShoppingCart<T> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
}
Kita bisa membuat:
const stringCart = new ShoppingCart<string>();
dan:
const numberCart = new ShoppingCart<number>();
Keduanya berasal dari class yang sama.
Tetapi masing-masing instance memiliki tipe yang berbeda.
13. Perhatikan perbedaannya
Generic
const stringCart = new ShoppingCart<string>();
Maka:
stringCart
↓
T = string
↓
items = string[]
↓
hanya menerima string
Kemudian:
const numberCart = new ShoppingCart<number>();
Maka:
numberCart
↓
T = number
↓
items = number[]
↓
hanya menerima number
Union
Kalau:
items: (string | number)[]
maka:
cart
↓
string atau number
↓
bisa campur
Tidak ada pemisahan berdasarkan instance.
14. Cara mengingat Generic vs Union
Gunakan kalimat ini:
Union:
“Value ini bisa tipe A atau tipe B.”
Contoh:
let id: string | number;
Artinya id bisa:
string
ATAU
number
Generic:
“Tipe ini akan ditentukan nanti, dan setelah ditentukan akan dipakai secara konsisten.”
Contoh:
const cart = new ShoppingCart<string>();
Artinya:
T ditentukan menjadi string
↓
cart khusus string
15. Contoh yang lebih sederhana
Misalnya kita punya function:
function printValue(value: string | number) {
console.log(value);
}
Kita bisa:
printValue("Hello");
printValue(100);
Ini cocok menggunakan Union.
Karena kita memang ingin:
“Function ini menerima string ATAU number.”
Tidak ada hubungan khusus antara input dan output.
Sekarang lihat:
function identity<T>(value: T): T {
return value;
}
Ini berbeda.
Kalau:
const result = identity("Hello");
maka:
T = string
sehingga:
input → string
output → string
Kalau:
const result = identity(100);
maka:
T = number
sehingga:
input → number
output → number
Generic mempertahankan hubungan antara tipe input dan output.
16. Ini contoh yang sangat penting
Bandingkan:
function identity(value: string | number): string | number {
return value;
}
dengan:
function identity<T>(value: T): T {
return value;
}
Dengan Union:
const result = identity("Hello");
TypeScript bisa melihat hasil sebagai:
string | number
karena function mengatakan:
string | number → string | number
Dengan Generic:
const result = identity("Hello");
TypeScript tahu:
T = string
sehingga:
string → string
Lebih spesifik.
17. Generic mempertahankan relationship
Ini adalah kata kunci yang sangat bagus untuk diingat:
Generic menjaga hubungan antar tipe.
Misalnya:
function wrap<T>(value: T): T[] {
return [value];
}
Kalau:
wrap("Hello");
hasilnya:
string[]
Kalau:
wrap(100);
hasilnya:
number[]
T menjaga hubungan:
Input T
↓
Output T[]
18. Generic pada class juga menjaga relationship
Kembali ke:
class ShoppingCart<T> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
}
Ada hubungan:
T
│
├── items: T[]
│
├── addItem(item: T)
│
└── getItems(): T[]
Misalnya:
ShoppingCart<Book>
maka semua T menjadi:
Book
Jadi:
items → Book[]
addItem → Book
getItems() → Book[]
Semua tetap konsisten.
19. Kenapa Union tidak menggantikan Generic?
Ini pertanyaan utama lecture.
Jawabannya:
Karena Union mendeskripsikan beberapa kemungkinan tipe, sedangkan Generic mempertahankan hubungan tipe yang dipilih untuk suatu penggunaan.
Contoh:
string | number
berarti:
string ATAU number
Sedangkan:
<T>
berarti:
Tipe belum ditentukan
↓
ditentukan saat digunakan
↓
setelah ditentukan tetap konsisten
20. Kapan menggunakan Union?
Gunakan Union kalau memang sebuah value secara alami boleh memiliki beberapa tipe.
Contohnya:
function formatId(id: string | number) {
return String(id);
}
id memang boleh berupa:
"USER-100"
atau:
100
Jadi Union cocok.
Contoh lain:
let status: "success" | "error" | "loading";
Ini juga cocok.
Karena status memang salah satu dari beberapa kemungkinan yang sudah ditentukan.
21. Kapan menggunakan Generic?
Gunakan Generic ketika kita ingin membuat kode reusable tetapi hubungan tipe harus dipertahankan.
Contohnya:
function first<T>(items: T[]): T {
return items[0];
}
Kalau:
const result = first([10, 20, 30]);
maka:
result: number
Kalau:
const result = first(["A", "B", "C"]);
maka:
result: string
Satu function bisa digunakan untuk berbagai tipe tanpa kehilangan informasi tipe.
22. Perbandingan langsung
| Generic | Union |
|---|---|
<T> | A | B |
| Tipe ditentukan kemudian | Kemungkinan tipe sudah ditentukan |
| Menjaga hubungan tipe | Menyatakan beberapa kemungkinan |
| Sangat bagus untuk reusable code | Bagus untuk value yang memang multi-type |
| Bisa menghasilkan tipe yang spesifik | Sering menghasilkan union |
| Bisa digunakan pada class/function/interface/type | Biasanya digunakan untuk menyatakan alternatif tipe |
Contoh Box<T> | Contoh string | number |
23. Analogi yang lebih gampang lagi
Bayangkan ada mesin pembuat botol.
Generic
Kita punya mesin:
BottleMachine<T>
Saat membeli mesin, kita menentukan:
BottleMachine<Water>
Mesin ini khusus membuat botol air.
Mesin lain:
BottleMachine<Juice>
khusus membuat botol jus.
Mesinnya sama, tetapi tipe produksinya ditentukan ketika digunakan.
Union
Kita punya satu botol yang boleh berisi:
Water ATAU Juice
Artinya satu botol tersebut bisa berisi salah satunya.
Jadi:
Generic → "mesinnya reusable, tipe ditentukan kemudian"
Union → "value-nya boleh salah satu dari beberapa tipe"
24. Generic bukan berarti “runtime”
Ada satu bagian lecture yang perlu kita luruskan sedikit.
Lecture menyebut tipe Generic ditentukan “during runtime”.
Secara konsep pembelajaran, maksudnya adalah ketika kita menggunakan/membuat instance atau memanggil function.
Tetapi secara teknis, TypeScript adalah compile-time type system.
Misalnya:
const cart = new ShoppingCart<string>();
<string> digunakan oleh TypeScript untuk pemeriksaan tipe saat development/compile.
JavaScript hasil compile tidak memiliki Generic TypeScript tersebut seperti:
ShoppingCart<string>
Jadi jangan menganggap:
“T baru ditentukan ketika program JavaScript sedang berjalan.”
Lebih tepat:
T ditentukan saat kita menggunakan generic dalam kode TypeScript, lalu TypeScript menggunakannya untuk type checking.
25. Contoh kompleks: Generic vs Union
Misalnya kita ingin membuat function:
function getFirst(items: string[] | number[]) {
return items[0];
}
Ini berarti function menerima:
string[] ATAU number[]
Tetapi hasilnya:
string | number
Karena TypeScript tidak selalu mempertahankan hubungan secara spesifik.
Dengan Generic:
function getFirst<T>(items: T[]): T {
return items[0];
}
Sekarang:
getFirst(["A", "B", "C"]);
menghasilkan:
string
Sedangkan:
getFirst([10, 20, 30]);
menghasilkan:
number
Lebih tepat.
26. Generic juga bisa digunakan untuk object
Misalnya:
function getValue<T>(value: T): T {
return value;
}
Bisa:
const user = getValue({
name: "John",
age: 30
});
TypeScript mempertahankan bentuk object tersebut.
Sedangkan Union:
type Data = User | Product;
berarti:
Data bisa User
ATAU
Product
Ini berbeda tujuan.
27. Hubungkan dengan type guard
Kita sebelumnya belajar Type Guard.
Kalau menggunakan Union:
function print(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
Kenapa perlu typeof?
Karena value memang bisa:
string
ATAU
number
Jadi kita harus menentukan:
“Saat ini value-nya yang mana?”
Dengan Generic:
function identity<T>(value: T): T {
return value;
}
kita tidak mengatakan:
T = string ATAU number
Kita mengatakan:
T = tipe yang dipilih untuk penggunaan ini
28. Kesalahan pemahaman yang sering terjadi
❌ Salah:
“Generic itu sama saja dengan Union karena keduanya bisa menerima banyak tipe.”
Tidak tepat.
✅ Yang benar:
Generic:
<T>
membuat satu kode reusable untuk berbagai tipe dengan hubungan tipe tetap terjaga.
Union:
A | B
menyatakan sebuah value bisa salah satu dari beberapa tipe.
29. Contoh super singkat
Union
function print(value: string | number) {
console.log(value);
}
Boleh:
print("Hello");
print(100);
Karena function memang menerima dua tipe.
Generic
function identity<T>(value: T): T {
return value;
}
Kalau:
identity("Hello");
maka:
T = string
Kalau:
identity(100);
maka:
T = number
30. Hubungkan dengan ShoppingCart
Generic
class ShoppingCart<T> {
private items: T[] = [];
addItem(item: T) {
this.items.push(item);
}
getItems(): T[] {
return this.items;
}
}
Kemudian:
const stringCart = new ShoppingCart<string>();
const numberCart = new ShoppingCart<number>();
Hasilnya:
stringCart
↓
string[]
↓
hanya string
numberCart
↓
number[]
↓
hanya number
Union
Kalau:
class ShoppingCart {
private items: (string | number)[] = [];
addItem(item: string | number) {
this.items.push(item);
}
}
Maka:
cart.addItem("Hello");
cart.addItem(100);
cart.addItem("World");
cart.addItem(200);
bisa menghasilkan:
["Hello", 100, "World", 200]
Karena setiap element boleh berbeda tipe.
31. Jadi Generic vs Union bukan soal “mana yang lebih bagus”
Ini juga penting.
Bukan berarti:
Generic lebih bagus daripada Union.
Tidak.
Mereka menyelesaikan masalah yang berbeda.
Gunakan Union ketika:
Satu value
boleh memiliki
beberapa tipe
Gunakan Generic ketika:
Kita ingin membuat kode reusable
untuk berbagai tipe
tetapi hubungan tipe
harus tetap konsisten
32. Cheat Sheet 🧠
Union
string | number
Baca:
string ATAU number
Contoh:
let value: string | number;
Value boleh:
"Hello"
100
Generic
<T>
Baca:
Tipe placeholder yang akan ditentukan kemudian
Contoh:
function identity<T>(value: T): T {
return value;
}
Jika:
identity<string>("Hello");
maka:
T = string
Jika:
identity<number>(100);
maka:
T = number
Generic Class
class ShoppingCart<T> {
items: T[];
}
Kemudian:
new ShoppingCart<Book>();
berarti:
T = Book
items = Book[]
Sedangkan:
new ShoppingCart<Cloth>();
berarti:
T = Cloth
items = Cloth[]
33. Kalimat kunci untuk interview
Kalau nanti ditanya:
“What is the difference between Generics and Union Types in TypeScript?”
Jawaban sederhananya:
Union Type digunakan ketika sebuah value dapat berupa salah satu dari beberapa tipe yang sudah ditentukan, misalnya
string | number. Generic digunakan untuk membuat kode yang reusable untuk berbagai tipe sambil mempertahankan hubungan dan type safety dari tipe yang dipilih.
Atau versi paling gampang:
UNION
A | B
↓
"bisa A ATAU B"
GENERIC
<T>
↓
"tipe belum ditentukan,
nanti pilih satu,
dan tetap konsisten"
🔥 Ingatan paling penting
Union = pilihan tipe pada sebuah value.
Generic = placeholder tipe untuk membuat kode reusable dan tetap type-safe.
Dan pada contoh ShoppingCart, inilah perbedaannya:
ShoppingCart<string>
berarti cart ini khusus string.
ShoppingCart<number>
berarti cart ini khusus number.
Sedangkan:
(string | number)[]
berarti satu array yang setiap elemennya boleh string atau number.
Itulah alasan Generic tidak bisa selalu digantikan oleh Union.