Masukkan Password
61 The keyOf Constraint
Lecture ini membahas salah satu fitur generic yang sangat berguna, yaitu keyof constraint.
Kalau lecture sebelumnya kita belajar:
<T extends object>
artinya:
“
Tharus berupa object.”
Sekarang kita ingin membuat aturan yang lebih spesifik:
“
Uharus berupa nama property yang memang ada diT.”
Dan inilah fungsi utama:
U extends keyof T
1. Masalah yang Ingin Diselesaikan
Kita ingin membuat function:
getPropValue()
yang tugasnya mengambil nilai property dari sebuah object.
Misalnya kita punya:
const user = {
name: "John",
age: 28
};
Kita ingin melakukan:
getPropValue(user, "name");
dan mendapatkan:
"John"
Atau:
getPropValue(user, "age");
mendapatkan:
28
Tetapi kita tidak ingin:
getPropValue(user, "gender");
Karena user tidak memiliki property:
gender
Nah, keyof digunakan untuk memastikan hal tersebut.
2. Versi Awal yang Bermasalah
Kita mungkin mulai dengan:
function getPropValue(obj: object, key: string) {
return obj[key];
}
Secara logika kelihatannya benar.
Kita punya:
obj → object
key → string
Kemudian:
obj[key]
artinya:
“Ambil property dari
objmenggunakan nama yang ada dikey.”
Misalnya:
const user = {
name: "John",
age: 28
};
dan:
key = "name"
maka:
obj[key]
menjadi:
obj["name"]
yang menghasilkan:
"John"
3. Tapi TypeScript Punya Masalah
Masalahnya adalah kita mengatakan:
key: string
Artinya semua string diperbolehkan.
Misalnya:
getPropValue(user, "name"); // ✅
getPropValue(user, "age"); // ✅
getPropValue(user, "gender"); // ❓
getPropValue(user, "address"); // ❓
getPropValue(user, "hello"); // ❓
Bagi kita sebagai manusia, jelas:
user
hanya punya:
name
age
Tetapi TypeScript melihat:
key: string
yang artinya:
key bisa string apa saja
Jadi TypeScript tidak memiliki jaminan bahwa:
obj[key]
benar-benar mengakses property yang ada.
4. Kita Butuh Hubungan Antara obj dan key
Ini inti dari lecture.
Kita tidak hanya ingin mengatakan:
obj = object
key = string
Kita ingin mengatakan:
key harus merupakan KEY dari obj
Misalnya:
const user = {
name: "John",
age: 28
};
Maka key yang valid adalah:
"name"
"age"
Sedangkan:
"gender"
"address"
"email"
tidak valid.
Kita membutuhkan sebuah mekanisme untuk mengambil semua key dari sebuah type.
Dan mekanisme itu adalah:
keyof
5. Apa Itu keyof?
keyof bisa dibaca sebagai:
“Ambil semua nama property/key yang dimiliki oleh sebuah type.”
Contoh:
type User = {
name: string;
age: number;
};
Kemudian:
type UserKeys = keyof User;
Maka secara konsep:
UserKeys = "name" | "age"
Jadi:
User
│
├── name
└── age
keyof User
│
└── "name" | "age"
Ini sangat penting.
6. keyof Menghasilkan Union of Keys
Misalnya:
type User = {
name: string;
age: number;
email: string;
};
Maka:
keyof User
menghasilkan:
"name" | "age" | "email"
Perhatikan tanda:
|
Itu adalah Union Type.
Artinya:
key harus salah satu dari:
"name"
"age"
"email"
Jadi:
let key: keyof User;
key = "name"; // ✅
key = "age"; // ✅
key = "email"; // ✅
key = "gender"; // ❌
7. Analogi Sederhana
Bayangkan kita memiliki rumah:
🏠 User
│
├── 🚪 name
├── 🚪 age
└── 🚪 email
keyof User seperti bertanya:
“Apa saja pintu yang tersedia di rumah ini?”
Jawabannya:
"name" | "age" | "email"
Kalau kita mengatakan:
key: keyof User
berarti:
“Kamu hanya boleh memilih pintu yang memang ada di rumah.”
Jadi:
"name" → ✅ ada
"age" → ✅ ada
"email" → ✅ ada
"house" → ❌ tidak ada
"color" → ❌ tidak ada
8. Sekarang Kita Gabungkan dengan Generic
Kita ingin function yang bisa bekerja dengan object apa pun.
Misalnya:
const user = {
name: "John",
age: 28
};
atau:
const product = {
title: "Laptop",
price: 1000
};
atau:
const car = {
brand: "Toyota",
year: 2025
};
Kita tidak ingin membuat function terpisah untuk setiap object.
Maka kita menggunakan generic:
function getPropValue<T>(obj: T) {
// ...
}
Tetapi kita juga ingin mengambil property berdasarkan key.
Maka kita membutuhkan generic kedua:
function getPropValue<T, U>(
obj: T,
key: U
) {
// ...
}
Sekarang:
T → tipe object
U → tipe key
Tetapi kita masih punya masalah.
U masih bisa apa saja.
9. Memberikan Constraint kepada U
Sekarang kita katakan:
U extends keyof T
Full-nya:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
) {
return obj[key];
}
Ini adalah inti utama lecture.
Bacanya:
“
Uadalah generic type yang harus merupakan salah satu key dariT.”
10. Bedah U extends keyof T
Ini kelihatannya rumit:
U extends keyof T
Mari pecah.
U
Generic kedua.
U = tipe key
keyof T
Semua key yang dimiliki oleh T.
extends
Memberikan constraint.
Jadi:
U extends keyof T
berarti:
“
Uhanya boleh berisi key yang terdapat diT.”
11. Contoh Konkret
Kita punya:
const user = {
name: "John",
age: 28
};
TypeScript mengetahui kira-kira:
T = {
name: string;
age: number;
}
Sekarang:
keyof T
menjadi:
"name" | "age"
Kemudian:
U extends keyof T
berarti:
U harus:
"name"
ATAU
"age"
Jadi:
getPropValue(user, "name");
✅ valid.
getPropValue(user, "age");
✅ valid.
Tetapi:
getPropValue(user, "gender");
❌ error.
Karena:
"gender"
tidak termasuk:
"name" | "age"
12. Visualisasi Lengkap
Misalnya:
const user = {
name: "John",
age: 28
};
Maka:
T
│
▼
┌─────────────────┐
│ name: string │
│ age: number │
└─────────────────┘
│
│ keyof T
▼
┌─────────────────┐
│ "name" | "age" │
└─────────────────┘
│
│
▼
U
harus salah satu
dari key tersebut
Jadi:
T = object
↓
keyof T
↓
"name" | "age"
↓
U harus salah satunya
13. Kenapa Ini Disebut “Constraint”?
Karena kita membatasi U.
Tanpa constraint:
function getPropValue<T, U>(
obj: T,
key: U
)
Artinya:
U bisa apa saja
Dengan constraint:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
)
artinya:
U harus merupakan key dari T
Jadi:
U
↓
bebas
berubah menjadi:
U
↓
keyof T
↓
harus key yang valid
14. Ini Lebih Kuat Daripada key: string
Bandingkan:
Cara pertama
function getPropValue<T>(
obj: T,
key: string
) {}
key bisa:
"name" ✅
"age" ✅
"gender" ❌ seharusnya
"hello" ❌ seharusnya
"abc" ❌ seharusnya
Tetapi TypeScript tidak punya informasi yang cukup untuk mencegah semuanya.
Dengan keyof
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
) {}
Sekarang:
"name" ✅
"age" ✅
"gender" ❌
"hello" ❌
"abc" ❌
TypeScript bisa mendeteksi kesalahan tersebut saat development/compile time.
15. Contoh Lain: Product
Misalnya:
const product = {
name: "Laptop",
price: 1500,
stock: 10
};
Maka:
keyof typeof product
secara konsep menghasilkan:
"name" | "price" | "stock"
Jadi:
getPropValue(product, "name"); // ✅
getPropValue(product, "price"); // ✅
getPropValue(product, "stock"); // ✅
Tetapi:
getPropValue(product, "color");
❌ karena:
"color"
tidak ada di product.
16. Generic Ini Bukan Hanya Memeriksa Object
Yang menarik adalah T bisa berbeda-beda.
Misalnya:
const user = {
name: "John",
age: 28
};
Maka:
T = User
dan:
keyof T = "name" | "age"
Kalau:
const product = {
title: "Laptop",
price: 1000
};
maka:
T = Product
dan:
keyof T = "title" | "price"
Jadi keyof T otomatis mengikuti object yang diberikan.
17. Ini yang Membuat Generic Sangat Powerful
Kita hanya membuat satu function:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
) {
return obj[key];
}
Tetapi function tersebut bisa bekerja dengan:
User
Product
Car
Book
Order
Student
Employee
dll.
Dan TypeScript otomatis mengetahui key yang valid untuk masing-masing object.
18. Return Type-nya Juga Pintar
Ini bagian yang sangat keren.
Misalnya:
const user = {
name: "John",
age: 28
};
Kemudian:
const name = getPropValue(user, "name");
TypeScript bisa mengetahui bahwa:
name = string
Sedangkan:
const age = getPropValue(user, "age");
TypeScript bisa mengetahui:
age = number
Jadi bukan hanya memastikan key valid.
TypeScript juga bisa mempertahankan hubungan antara key dan value.
19. Kenapa name Jadi string?
Karena object kita:
const user = {
name: "John",
age: 28
};
memiliki tipe:
{
name: string;
age: number;
}
Ketika kita melakukan:
getPropValue(user, "name");
TypeScript tahu:
"name"
↓
property name
↓
string
Maka return value:
string
Sedangkan:
getPropValue(user, "age");
alur:
"age"
↓
property age
↓
number
Maka return:
number
20. Hubungannya dengan keyof + Indexed Access Type
Ada konsep TypeScript yang sangat erat dengan ini.
Secara type-level, kita bisa mengambil tipe value berdasarkan key dengan:
T[U]
Jadi function yang lebih eksplisit bisa ditulis:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
): T[U] {
return obj[key];
}
Perhatikan:
T[U]
Ini bukan mengambil value secara runtime.
Ini memberitahu TypeScript:
“Return type adalah tipe property
Uyang terdapat padaT.”
21. Contoh T[U]
Misalnya:
type User = {
name: string;
age: number;
};
Kalau:
type Name = User["name"];
hasilnya:
string
Kalau:
type Age = User["age"];
hasilnya:
number
Kalau:
type Value = User[keyof User];
hasilnya secara konsep:
string | number
Karena:
keyof User
=
"name" | "age"
Maka:
User["name" | "age"]
=
string | number
Ini memang sedikit advanced, tetapi bagus untuk melihat betapa kuatnya hubungan antara:
keyof
+
generic
+
indexed access
22. Analogi keyof yang Sangat Sederhana
Bayangkan sebuah menu restoran:
🍽️ Menu
├── nasi goreng
├── mie goreng
├── sate
└── soto
Kalau kita memiliki:
keyof Menu
maka hasilnya adalah:
"nasi goreng"
| "mie goreng"
| "sate"
| "soto"
Sekarang kita membuat function:
getMenu(menu, pilihan)
Kita ingin pilihan hanya boleh memilih menu yang tersedia.
Dengan:
U extends keyof T
kita mengatakan:
“Pilihan hanya boleh berasal dari daftar menu yang memang tersedia.”
Jadi:
"sate" → ✅
"soto" → ✅
"pizza" → ❌
"hamburger" → ❌
23. keyof Tidak Mengambil Value
Ini kesalahan yang cukup umum.
Misalnya:
type User = {
name: string;
age: number;
};
keyof User menghasilkan:
"name" | "age"
Bukan:
string | number
Karena keyof mengambil nama property, bukan tipe value.
Jadi:
keyof User
↓
"name" | "age"
Sedangkan:
User[keyof User]
mengambil tipe value:
string | number
24. Bedakan keyof dan typeof
Keduanya sering muncul di TypeScript dan bisa membingungkan.
typeof
Digunakan untuk mendapatkan tipe dari sebuah value.
Misalnya:
const user = {
name: "John",
age: 28
};
Kemudian:
typeof user
memberikan tipe:
{
name: string;
age: number;
}
keyof
Digunakan untuk mengambil key dari sebuah type:
keyof typeof user
hasilnya:
"name" | "age"
Jadi:
typeof user
↓
tipe object
keyof typeof user
↓
key dari object
25. Contoh Lengkap
Berikut versi yang lebih lengkap dan type-safe:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
): T[U] {
return obj[key];
}
Gunakan:
const user = {
name: "John",
age: 28
};
const name = getPropValue(user, "name");
const age = getPropValue(user, "age");
TypeScript mengetahui:
name → string
age → number
Tetapi:
const gender = getPropValue(user, "gender");
akan error karena:
"gender"
bukan key dari user.
26. Kenapa Kita Butuh Dua Generic?
Ini juga penting.
Kita punya:
<T, U extends keyof T>
T
Mewakili object.
T = object
U
Mewakili key.
U = key dari T
Jadi ada hubungan:
T
↓
object
keyof T
↓
semua key object
U extends keyof T
↓
U harus salah satu key tersebut
27. Visualisasi Hubungan T dan U
T
│
▼
┌─────────────────┐
│ name: string │
│ age: number │
│ email: string │
└─────────────────┘
│
│ keyof
▼
"name" | "age" | "email"
│
│
▼
U
harus salah satu
Jadi U tidak berdiri sendiri.
U bergantung pada T.
Ini yang membuat generic constraint tersebut sangat powerful.
28. Bandingkan dengan Generic Biasa
Generic bebas
function getSomething<T, U>(
obj: T,
key: U
) {}
Hubungannya:
T → object
U → bebas
Tidak ada hubungan antara T dan U.
Generic dengan keyof
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
) {}
Hubungannya:
T
↓
keyof T
↓
U
Sekarang U tergantung pada T.
Ini yang kita inginkan.
29. Hubungan dengan Generic Constraint Sebelumnya
Sebelumnya kita belajar:
<T extends object>
Artinya:
T harus object
Sekarang:
<U extends keyof T>
artinya:
U harus key dari T
Jadi sebenarnya keduanya menggunakan konsep yang sama:
extends = constraint
Tetapi constraint-nya berbeda.
Constraint object
T extends object
T → harus object
Constraint key
U extends keyof T
U → harus key dari T
30. Contoh dengan Interface
Tidak harus menggunakan object literal.
Kita juga bisa menggunakan interface:
interface User {
name: string;
age: number;
email: string;
}
Kemudian:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
): T[U] {
return obj[key];
}
Gunakan:
const user: User = {
name: "John",
age: 28,
email: "john@example.com"
};
Valid:
getPropValue(user, "name");
getPropValue(user, "age");
getPropValue(user, "email");
Tidak valid:
getPropValue(user, "gender");
31. Kenapa Ini Berguna di Dunia Nyata?
Pattern seperti ini sangat berguna ketika kita membuat function yang bekerja secara generic terhadap object.
Misalnya:
Membaca property
getPropValue(user, "name");
Mengambil data dari object
getProperty(product, "price");
Utility function
getValue(config, "timeout");
Sorting berdasarkan property
Misalnya secara konsep:
sortBy(users, "age");
atau:
sortBy(users, "name");
Kita bisa memastikan property yang diberikan memang ada.
32. TypeScript Membantu Menangkap Typo
Ini salah satu manfaat paling nyata.
Misalnya kita tidak sengaja menulis:
getPropValue(user, "nmae");
Padahal seharusnya:
"name"
Dengan:
U extends keyof T
TypeScript langsung bisa mengatakan:
❌ "nmae" bukan key dari user
Jadi typo bisa ditangkap sebelum program dijalankan.
33. Ini Lebih Aman daripada String Biasa
Kalau kita hanya menggunakan:
key: string
TypeScript berpikir:
"anything" → string
Semua string secara type-level dianggap valid.
Dengan:
key: U
dan:
U extends keyof T
TypeScript berkata:
key harus berasal dari object tersebut
Jadi kita berpindah dari:
string bebas
menjadi:
string yang valid berdasarkan object
34. Ringkasan Semua Konsep
Mari kita rangkum:
keyof
keyof User
mengambil semua key dari User.
Misalnya:
type User = {
name: string;
age: number;
};
maka:
keyof User
adalah:
"name" | "age"
Generic
<T>
membuat function fleksibel terhadap tipe.
Multiple Generic
<T, U>
memungkinkan kita mempunyai dua tipe berbeda.
Generic Constraint
<T extends object>
membatasi T agar harus object.
keyof Constraint
<U extends keyof T>
membatasi U agar harus merupakan key dari T.
35. Cheat Sheet
type User = {
name: string;
age: number;
email: string;
};
Semua key:
keyof User
hasil:
"name" | "age" | "email"
Function generic:
function test<T>(value: T) {}
Generic + object constraint:
function test<T extends object>(value: T) {}
Generic + keyof constraint:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
): T[U] {
return obj[key];
}
36. Cara Membaca U extends keyof T
Kalau melihat kode ini:
U extends keyof T
jangan langsung takut. Pecah saja:
U
│
│ "Tipe key yang kita pilih"
│
▼
extends
│
│ "harus memenuhi constraint"
│
▼
keyof T
│
│ "semua key yang dimiliki T"
│
▼
U harus menjadi salah satu key dari T
Atau dalam bahasa manusia:
"
Uhanya boleh berisi nama property yang memang ada diT."
37. Inti Lecture dalam Satu Contoh
Perhatikan kode ini:
function getPropValue<T, U extends keyof T>(
obj: T,
key: U
): T[U] {
return obj[key];
}
Baca seperti kalimat:
“Buat function
getPropValueyang menerima object dengan tipeT, kemudian menerima key dengan tipeU. TetapiUtidak boleh sembarang tipe;Uharus merupakan salah satu key dariT. Kemudian kembalikan value dari property tersebut.”
Misalnya:
const user = {
name: "John",
age: 28
};
Ketika:
getPropValue(user, "name");
TypeScript secara konsep mengetahui:
T = {
name: string;
age: number;
}
keyof T = "name" | "age"
U = "name"
T[U] = string
Jadi hasil:
string
Kalau:
getPropValue(user, "age");
maka:
U = "age"
T[U] = number
Jadi hasil:
number
Sedangkan:
getPropValue(user, "gender");
ditolak karena:
"gender" ∉ keyof T
38. Kalimat yang Paling Penting
Kalau hanya menghafalkan satu hal dari lecture ini, hafalkan:
keyof Tberarti semua key yang dimiliki oleh tipeT.
Dan:
U extends keyof TberartiUhanya boleh menjadi salah satu key dariT.
Visual paling singkatnya:
T = {
name: string
age: number
}
↓ keyof
"name" | "age"
↓ U extends keyof T
U hanya boleh:
"name" atau "age"
Jadi keyof constraint adalah cara TypeScript untuk membuat hubungan yang sangat kuat antara sebuah object dan property yang boleh diakses darinya. Ini membuat kode generic tetap fleksibel, tetapi TypeScript tetap bisa menangkap kesalahan seperti property yang tidak ada atau typo sejak awal development.
Saya juga bisa membuat gambar/diagram visual alur T → keyof T → U → T[U] agar konsep ini lebih mudah diingat.