Masukkan Password
60 Bekerja dengan Batasan (Constraints)
Lecture ini melanjutkan langsung materi sebelumnya tentang Generic Function, dan fokus utamanya adalah Generic Constraints.
Kalau di lecture sebelumnya kita belajar:
“Saya punya tipe
T, tetapi saya belum tahuTitu apa.”
Maka di lecture ini kita belajar:
“Saya memang belum tahu
Titu apa, tetapi saya ingin membatasiTsupaya hanya boleh tipe tertentu.”
Ini konsep yang sangat penting dalam TypeScript.
1. Apa Itu Generic Constraint?
Secara sederhana:
Generic Constraint adalah aturan/batasan yang kita berikan kepada generic type supaya tidak bisa menerima sembarang tipe.
Misalnya generic biasa:
function test<T>(value: T) {
// ...
}
T bisa apa saja:
T = number
T = string
T = boolean
T = object
T = array
T = User
...
Tetapi kita bisa mengatakan:
function test<T extends object>(value: T) {
// ...
}
Sekarang T tidak boleh sembarangan.
Kita mengatakan:
T harus memenuhi constraint object
Jadi secara sederhana:
<T>
↓
T boleh apa saja
<T extends object>
↓
T harus object
2. Analogi Sederhana
Bayangkan kita punya sebuah pintu.
Generic biasa:
🚪 Pintu
"Semua tipe boleh masuk"
Jadi:
number → boleh
string → boleh
boolean → boleh
object → boleh
array → boleh
Tetapi generic constraint:
<T extends object>
seperti security guard:
🚪 Pintu
"Yang boleh masuk hanya OBJECT."
Maka:
object → ✅
number → ❌
string → ❌
boolean → ❌
Jadi constraint memberikan aturan masuk untuk generic.
3. Mengapa Kita Membutuhkan Constraint?
Mari kita kembali ke contoh dari lecture sebelumnya.
Kita mempunyai function:
function merge<T, U>(obj1: T, obj2: U) {
return Object.assign(obj1, obj2);
}
Tujuan kita adalah menggabungkan dua object.
Misalnya:
const obj1 = {
name: "John",
age: 28
};
const obj2 = {
name: "John",
gender: "male"
};
Kemudian:
const combined = merge(obj1, obj2);
Kita ingin hasilnya:
{
name: "John",
age: 28,
gender: "male"
}
Masalahnya adalah generic:
<T, U>
membolehkan:
T = apa saja
U = apa saja
Padahal function kita sebenarnya menginginkan object.
4. Masalah Kalau Tidak Ada Constraint
Perhatikan:
function merge<T, U>(
obj1: T,
obj2: U
) {
return Object.assign(obj1, obj2);
}
Karena tidak ada constraint, TypeScript secara konsep membolehkan:
merge(100, 200);
Maka:
T = number
U = number
Atau:
merge("Hello", "World");
maka:
T = string
U = string
Padahal function merge() yang sedang kita buat memang ditujukan untuk object.
Jadi kita perlu mengatakan:
“Tolong TypeScript,
TdanUhanya boleh berupa object.”
5. Syntax Generic Constraint
Syntax-nya:
<T extends object>
Kalau ada dua generic:
<T extends object, U extends object>
Contoh lengkap:
function merge<T extends object, U extends object>(
obj1: T,
obj2: U
) {
return Object.assign(obj1, obj2);
}
Sekarang kita memberikan constraint:
T → harus object
U → harus object
6. Bedah Syntax-nya
Perhatikan:
<T extends object, U extends object>
Ada empat bagian penting.
< >
Menandakan kita sedang mendefinisikan generic type parameter.
T
Nama generic pertama.
extends
Digunakan untuk memberikan constraint.
object
Constraint yang kita berikan.
Jadi:
T extends object
dibaca:
“
Tboleh berupa tipe apa pun, asalkan memenuhi constraintobject.”
7. extends di Generic Berarti Apa?
Ini penting karena sebelumnya kita juga pernah melihat:
class Dog extends Animal
Pada class:
Dog extends Animal
extends berarti inheritance/pewarisan.
Tetapi pada generic:
<T extends object>
maknanya berbeda.
Di sini extends berarti:
T harus memenuhi batasan/constraint tertentu.
Jadi jangan selalu mengartikan:
extends = inheritance
Kalau konteksnya generic:
<T extends X>
lebih mudah dibaca sebagai:
“
Tdibatasi olehX.”
8. Apa yang Terjadi Setelah Kita Menambahkan Constraint?
Misalnya:
function merge<T extends object, U extends object>(
obj1: T,
obj2: U
) {
return Object.assign(obj1, obj2);
}
Sekarang:
merge(
{ name: "John" },
{ age: 28 }
);
✅ Boleh.
Karena keduanya object.
Tetapi:
merge(
100,
{ age: 28 }
);
❌ Error.
Karena:
T = number
sedangkan kita menetapkan:
T harus object
Begitu juga:
merge(
{ name: "John" },
"Hello"
);
❌ Error.
Karena:
U = string
sedangkan:
U harus object
9. Visualisasi Constraint
Tanpa constraint:
function merge<T, U>()
T ────────────────► bebas
│
├── number
├── string
├── boolean
├── object
└── dll
Dengan:
function merge<T extends object, U extends object>()
menjadi:
T ────────────────► harus object
U ────────────────► harus object
Atau:
merge<T, U>
│
┌────────┴────────┐
│ │
T extends object U extends object
│ │
▼ ▼
object object
10. Kembali ke Object.assign()
Sekarang kita bisa memahami mengapa lecture membutuhkan constraint.
Sebelumnya kita punya:
function merge<T, U>(
obj1: T,
obj2: U
) {
return Object.assign(obj1, obj2);
}
TypeScript berpikir:
“T ini sebenarnya apa?”
Bisa saja:
number
string
boolean
object
...
Jadi TypeScript tidak bisa membuat asumsi bahwa T adalah object.
Setelah kita menulis:
function merge<T extends object, U extends object>(
obj1: T,
obj2: U
)
TypeScript sekarang tahu:
T = object atau tipe yang memenuhi object constraint
U = object atau tipe yang memenuhi object constraint
Sehingga jauh lebih aman untuk operasi yang membutuhkan object.
11. Hasil Return Type-nya Juga Menjadi Lebih Pintar
Ini bagian yang menarik.
Misalnya:
const obj1 = {
name: "John",
age: 28
};
const obj2 = {
name: "John",
gender: "male"
};
Kemudian:
const combined = merge(obj1, obj2);
TypeScript bisa mengetahui bahwa:
T =
{
name: string;
age: number;
}
U =
{
name: string;
gender: string;
}
Dan hasil Object.assign() secara type-level dapat direpresentasikan sebagai gabungan keduanya.
Secara konsep:
T & U
Jadi hasilnya kira-kira:
{
name: string;
age: number;
gender: string;
}
Itulah sebabnya:
combined.name
bisa digunakan.
Dan:
combined.age
juga diketahui.
Begitu pula:
combined.gender
12. Hubungannya dengan Intersection Type
Ini nyambung banget dengan materi yang sudah kita pelajari sebelumnya.
Kita pernah belajar:
type User = {
name: string;
age: number;
};
type Admin = {
name: string;
role: string;
};
type AdminUser = User & Admin;
Maka:
AdminUser
=
User AND Admin
hasilnya:
{
name: string;
age: number;
role: string;
}
Pada merge() kita punya:
T
+
U
yang secara type-level dapat dipandang sebagai:
T & U
Jadi:
Object 1
+
Object 2
↓
T & U
↓
Combined Object
13. Kenapa T dan U Harus Sama-sama Diberi Constraint?
Misalnya kita hanya menulis:
function merge<T extends object, U>(
obj1: T,
obj2: U
) {
// ...
}
Sekarang:
T → harus object
U → bebas
Jadi:
merge(
{ name: "John" },
100
);
masih memungkinkan secara generic karena:
T = object
U = number
Padahal kita ingin dua-duanya object.
Maka:
<T extends object, U extends object>
lebih sesuai.
14. Ini Seperti Memberikan “Syarat”
Tanpa constraint:
<T>
artinya:
“Silakan pilih tipe apa saja.”
Dengan constraint:
<T extends object>
artinya:
“Silakan pilih tipe apa saja, tetapi harus memenuhi syarat object.”
Dengan constraint lain:
<T extends string>
artinya:
“
Tharus memenuhi constraint string.”
Begitu juga:
<T extends number>
memberikan batasan number.
15. Constraint Tidak Harus object
Lecture juga mengatakan bahwa kita bisa menggunakan tipe lain.
Contohnya:
<T extends string>
atau:
<T extends number>
atau bahkan tipe object tertentu.
Misalnya:
function printName<T extends { name: string }>(
value: T
) {
console.log(value.name);
}
Ini bahkan lebih powerful.
Kita tidak sekadar mengatakan:
“
Tharus object.”
Kita mengatakan:
“
Tharus memiliki propertynameyang bertipestring.”
16. Contoh Constraint yang Lebih Berguna
Misalnya:
function printName<T extends { name: string }>(
user: T
) {
console.log(user.name);
}
Sekarang:
printName({
name: "John",
age: 28
});
✅ Bisa.
Karena object tersebut punya:
name: string
printName({
name: "Alice",
gender: "female"
});
✅ Bisa.
Karena juga punya:
name: string
Tetapi:
printName({
age: 28
});
❌ Tidak bisa.
Karena tidak mempunyai:
name: string
17. Ini Kelebihan Generic Constraint
Generic biasa:
<T>
hanya berkata:
“Saya tidak tahu tipe T.”
Generic constraint:
<T extends { name: string }>
berkata:
“Saya tidak tahu tipe detail T, tetapi saya tahu T pasti memiliki
name: string.”
Jadi kita mendapatkan dua keuntungan sekaligus:
Generic
+
Constraint
=
Flexible
+
Type Safe
18. Analogi yang Lebih Mudah
Bayangkan kita membuat function untuk orang yang mempunyai kartu identitas.
Generic biasa:
<T>
artinya:
“Saya menerima benda apa pun.”
Bisa:
📱 HP
📕 Buku
🚗 Mobil
👤 Orang
Tetapi generic constraint:
<T extends { name: string }>
artinya:
“Saya menerima benda apa pun, asal benda itu memiliki
name.”
Jadi:
👤 User
name ✅
👨💼 Employee
name ✅
👨🎓 Student
name ✅
📱 Phone
name ❓
TypeScript akan memastikan syarat tersebut terpenuhi.
19. Generic Constraint vs Type Guard
Ini sering membingungkan karena keduanya sama-sama berhubungan dengan “pengecekan tipe”, tetapi konsepnya berbeda.
Generic Constraint
Dilakukan pada saat mendefinisikan generic.
function test<T extends object>(value: T) {}
Artinya:
“
Tharus object.”
Type Guard
Dilakukan untuk memeriksa value saat runtime.
if (typeof value === "string") {
// value dianggap string
}
Jadi:
Generic Constraint
↓
Memberikan aturan tipe
Type Guard
↓
Memeriksa / mempersempit tipe value
20. Constraint vs Assertion
Kita juga sudah belajar Type Assertion:
const input = element as HTMLInputElement;
Assertion mengatakan:
“Percayalah kepada saya, ini
HTMLInputElement.”
Sedangkan constraint:
<T extends object>
mengatakan:
“TypeScript, jangan izinkan tipe yang tidak memenuhi aturan ini.”
Jadi:
Assertion
→ saya yang memberi tahu compiler
Constraint
→ compiler yang memastikan syarat generic terpenuhi
21. Constraint Tidak Mengubah Data
Ini juga penting.
Ketika kita menulis:
<T extends object>
TypeScript tidak mengubah:
100
menjadi object.
Constraint hanya mengatakan:
100 ❌ tidak memenuhi aturan
Jadi constraint bekerja pada type system, bukan mengubah value ketika program berjalan.
22. Contoh Sederhana dari Nol
Mari kita buat function:
function getProperty<T, K extends keyof T>(
obj: T,
key: K
) {
return obj[key];
}
Ini memang sedikit lebih advanced, tetapi bagus untuk melihat kekuatan constraint.
Misalnya:
const user = {
name: "John",
age: 28
};
Kemudian:
getProperty(user, "name");
✅ Bisa.
getProperty(user, "age");
✅ Bisa.
Tetapi:
getProperty(user, "gender");
❌ Error.
Kenapa?
Karena:
K extends keyof T
membatasi K hanya boleh menjadi key yang benar-benar dimiliki oleh T.
Ini menunjukkan bahwa constraint bisa jauh lebih spesifik daripada sekadar:
T extends object
23. Jadi Constraint Bisa Bertingkat
Kita bisa memiliki:
<T extends object>
atau:
<T extends { name: string }>
atau:
<T extends SomeInterface>
atau:
<K extends keyof T>
Semuanya menggunakan ide yang sama:
Generic boleh fleksibel, tetapi tetap harus memenuhi syarat tertentu.
24. Kembali ke Contoh merge
Versi sederhananya:
function merge<T extends object, U extends object>(
obj1: T,
obj2: U
) {
return Object.assign(obj1, obj2);
}
Pemakaian:
const user = {
name: "John",
age: 28
};
const details = {
gender: "male"
};
const combined = merge(user, details);
Secara konsep:
T =
{
name: string;
age: number;
}
U =
{
gender: string;
}
Hasil:
T & U
yaitu:
{
name: string;
age: number;
gender: string;
}
25. Apa yang Terjadi Jika Kita Kirim Number?
merge(user, 100);
TypeScript melihat:
T = object
U = number
Constraint mengatakan:
U extends object
Tetapi:
number ≠ object
Maka:
❌ Error
Jadi error terjadi sebelum program dijalankan.
Ini salah satu manfaat besar TypeScript.
26. Alur Pemikiran Lecture
Kalau kita sederhanakan seluruh lecture:
Kita punya function merge()
↓
Kita ingin menerima dua object
↓
Object pertama dan kedua bisa punya
struktur yang berbeda
↓
Gunakan generic
↓
<T, U>
↓
Masalah:
T dan U bisa menerima tipe apa saja
↓
Kita ingin membatasi mereka
↓
Gunakan extends
↓
<T extends object, U extends object>
↓
Sekarang T dan U
harus berupa object
27. Hubungan dengan Materi-Materi Sebelumnya
Kalau semua materi kita gabungkan:
UNION
string | number
↓
"ATAU"
INTERSECTION
A & B
↓
"AND"
GENERIC
<T>
↓
"placeholder tipe"
MULTIPLE GENERIC
<T, U>
↓
"beberapa placeholder tipe"
CONSTRAINT
<T extends object>
↓
"placeholder tipe + batasan"
TYPE GUARD
typeof / instanceof / in
↓
"cek dan narrow tipe"
Masing-masing memiliki tugas berbeda.
28. Cheat Sheet Generic Constraint
Generic biasa
function test<T>(value: T) {}
Artinya:
Tbisa tipe apa saja.
Constraint object
function test<T extends object>(value: T) {}
Artinya:
Tharus object.
Constraint string
function test<T extends string>(value: T) {}
Artinya:
Tharus memenuhi constraint string.
Constraint number
function test<T extends number>(value: T) {}
Artinya:
Tharus memenuhi constraint number.
Constraint berdasarkan struktur
function test<T extends { name: string }>(
value: T
) {}
Artinya:
Tharus memilikinamebertipestring.
Multiple constraints
function merge<
T extends object,
U extends object
>(
obj1: T,
obj2: U
) {}
Artinya:
T → harus object
U → harus object
29. Perbedaan Paling Penting
Ingat pola ini:
<T>
↓
“Saya belum tahu tipenya.”
Sedangkan:
<T extends object>
↓
“Saya belum tahu tipe detailnya, tetapi saya tahu tipe tersebut harus object.”
Dan:
<T extends { name: string }>
↓
“Saya belum tahu tipe detailnya, tetapi saya tahu tipe tersebut harus punya
name: string.”
Jadi Generic Constraint sebenarnya bukan menghilangkan fleksibilitas generic.
Justru:
Generic Constraint memberikan fleksibilitas yang terkontrol.
30. Kalimat Inti yang Sebaiknya Kamu Hafalkan
Kalau cuma ingin mengingat satu definisi:
Generic Constraint adalah cara untuk memberikan batasan pada generic type menggunakan
extends, sehingga generic tetap fleksibel tetapi hanya boleh menerima tipe yang memenuhi syarat tertentu.
Contoh paling dasar:
<T extends object>
dibaca:
"
Tboleh tipe apa saja, tetapi harus berupa object."
Sedangkan untuk lecture ini:
function merge<T extends object, U extends object>(
obj1: T,
obj2: U
) {
return Object.assign(obj1, obj2);
}
bisa dibaca dengan bahasa manusia:
“Buat function
mergeyang menerima dua tipe data berbeda,TdanU. Keduanya tetap fleksibel, tetapi saya membatasi bahwaTdanUharus berupa object. Setelah itu gabungkan kedua object tersebut.”
Dan ini adalah inti dari Generic Constraint: flexible, but with rules.