Batasan Generics

Membatasi parameter tipe dengan extends, keyof, dan nilai bawaan parameter tipe.

Daftar isi

Parameter tipe tanpa batasan hanya menjanjikan keberadaan nilai, tanpa memberi tahu anggota apa yang tersedia. Batasan (constraint) memperketat parameter tipe dengan kata kunci extends, sehingga compiler mengetahui bentuk minimum yang dimiliki T. Topik ini membahas batasan berupa bentuk objek, batasan dengan keyof, serta nilai bawaan (default) pada parameter tipe.

Membatasi T dengan extends#

Klausa extends setelah parameter tipe menyatakan syarat minimum yang harus dipenuhi argumennya. Di dalam badan fungsi, T selanjutnya diperlakukan memiliki setidaknya bentuk yang disyaratkan, sehingga properti yang dijanjikan batasan boleh diakses langsung.

ts batasan-extends.ts
function terpanjang<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(terpanjang("buku", "majalah"));
console.log(terpanjang([1, 2], [1, 2, 3]));
text Keluaran
majalah
[ 1, 2, 3 ]

Batasan { length: number } dipenuhi oleh string maupun array, sehingga fungsi terpanjang dapat dipanggil dengan keduanya tanpa menduplikasi implementasinya. Compiler menolak argumen yang tidak memiliki properti length, seperti number, sebagaimana ditunjukkan berkas berikut.

ts batasan-extends-salah.ts
// tidak-dijalankan
function terpanjang<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b;
}

console.log(terpanjang(42, 7));
bash
# berkas ini sengaja tidak lolos pemeriksaan tipe
tsc --noEmit batasan-extends-salah.ts
text Keluaran
batasan-extends-salah.ts(6,24): error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

Pesan error di atas menyebut parameter bertipe { length: number; } karena itulah satu-satunya yang diketahui compiler tentang T. Tanpa batasan, error tidak muncul di titik pemanggilan, melainkan di badan fungsi ketika a.length ditulis. Dengan batasan, pemeriksaan berpindah ke tempat yang lebih dekat dengan penyebabnya.

Batasan tidak harus berupa bentuk anonim, karena nama tipe yang sudah ada juga sah, misalnya T extends Baris bila Baris adalah interface yang telah dideklarasikan. Dua parameter tipe dalam satu fungsi boleh memiliki batasan yang berbeda, dan sebuah batasan bahkan boleh merujuk ke parameter tipe lain. Pola K extends keyof T pada bagian berikutnya memanfaatkan rujukan antarparameter semacam ini.

Membatasi kunci dengan keyof#

Operator tipe keyof menghasilkan union seluruh nama properti sebuah tipe. Dipadukan dengan extends pada parameter tipe kedua, keyof menghasilkan pola akses properti yang aman, yaitu fungsi yang hanya menerima nama properti yang memang ada.

ts batasan-keyof.ts
interface Siswa {
  nama: string;
  nilai: number;
}

function ambil<T, K extends keyof T>(data: T, kunci: K): T[K] {
  return data[kunci];
}

const siswa: Siswa = { nama: "Dewi", nilai: 88 };

console.log(ambil(siswa, "nama"));
console.log(ambil(siswa, "nilai"));
text Keluaran
Dewi
88

Parameter tipe K dibatasi oleh keyof T, sehingga hanya "nama" dan "nilai" yang diterima. Tipe kembaliannya ditulis T[K], yaitu tipe properti yang sesuai dengan kunci yang diminta, sehingga hasil "nilai" bertipe number dan hasil "nama" bertipe string. Kunci lain ditolak dengan pesan yang menyebut daftar kunci yang sah, sebagaimana ditunjukkan berkas berikut.

ts batasan-keyof-salah.ts
// tidak-dijalankan
interface Siswa {
  nama: string;
  nilai: number;
}

function ambil<T, K extends keyof T>(data: T, kunci: K): T[K] {
  return data[kunci];
}

const siswa: Siswa = { nama: "Dewi", nilai: 88 };

console.log(ambil(siswa, "kelas"));
bash
# berkas ini sengaja tidak lolos pemeriksaan tipe
tsc --noEmit batasan-keyof-salah.ts
text Keluaran
batasan-keyof-salah.ts(13,26): error TS2345: Argument of type '"kelas"' is not assignable to parameter of type 'keyof Siswa'.

Kunci kelas tidak ada pada Siswa, sehingga argumennya ditolak dengan menyebut keyof Siswa sebagai tipe yang diharapkan. Daftar kunci itu tidak ditulis secara manual, melainkan diturunkan dari tipe, sehingga perubahan pada interface otomatis diikuti oleh pemeriksaan. Pola ini juga menjadi dasar utilitas seperti Pick dan Record untuk kebutuhan tampilan dan kamus.

Tipe hasil T[K] disebut indexed access type, yaitu tipe yang diambil dari properti tertentu sebuah tipe lain. Apabila T memiliki index signature, yaitu deklarasi yang menerima kunci bebas, keyof T menghasilkan string atau number sesuai jenis kuncinya, sehingga batasannya menjadi lebih longgar. Untuk tipe objek biasa, hasilnya berupa union nama properti, sebagaimana terlihat pada pesan error di atas.

Pola keyof juga lazim dipadukan dengan typeof pada konstanta objek, misalnya keyof typeof PENGATURAN yang menghasilkan kunci-kunci dari nilai konstanta tersebut. Dengan begitu, daftar kunci yang sah diambil dari data yang benar-benar dipakai program, bukan dari tipe yang ditulis terpisah. Jumlah deklarasi yang harus dijaga sinkron pun berkurang, karena perubahan pada konstanta langsung mengubah daftar kuncinya.

Nilai bawaan parameter tipe#

Parameter tipe boleh diberi nilai bawaan dengan tanda sama dengan, sama seperti parameter fungsi. Apabila pemanggil tidak mengisi parameter tipe tersebut, nilai bawaan itulah yang dipakai. Nilai bawaan boleh merujuk ke parameter tipe yang dideklarasikan sebelumnya, sehingga rantai tipe dapat dibangun secara bertahap.

ts batasan-default.ts
interface HasilAmbil<T = string> {
  data: T;
  status: "ok" | "gagal";
}

const arsip: HasilAmbil = { data: "laporan-2026.zip", status: "ok" };
const deret: HasilAmbil<number[]> = { data: [10, 20, 30], status: "ok" };

console.log(arsip.data, arsip.status);
console.log(deret.data);
text Keluaran
laporan-2026.zip ok
[ 10, 20, 30 ]

Deklarasi HasilAmbil tanpa argumen tipe menghasilkan data bertipe string, sedangkan HasilAmbil<number[]> mengisinya dengan array number. Nilai bawaan mengurangi kebisingan pada pemakaian yang paling sering tanpa menghilangkan fleksibilitas untuk kasus lain. Aturan penulisannya hanya satu, yaitu parameter tipe yang memiliki nilai bawaan harus diletakkan setelah parameter tipe wajib.

Nilai bawaan boleh merujuk parameter tipe sebelumnya, misalnya <T, U = T[]> yang menjadikan U bertipe array dari T apabila tidak diisi. Kombinasinya dengan batasan juga sah, misalnya <T extends string = string>, dengan syarat urutan yang sama tetap ditaati. Pemilihan nilai bawaan biasanya mengikuti pemakaian yang paling umum, sehingga tanda tangan tetap ringkas untuk sebagian besar kasus.

Kesalahan umum#

Kekeliruan pertama adalah lupa menambahkan extends sebelum mengakses properti pada T. Parameter tipe polos tidak menjanjikan anggota apa pun, sehingga akses seperti data.nama menghasilkan error TS2339 di badan fungsi. Perbaikannya adalah menyatakan bentuk minimum yang benar-benar diakses, misalnya T extends { nama: string }, tanpa memperlebar batasan melebihi kebutuhan.

Kekeliruan kedua adalah meletakkan parameter tipe dengan nilai bawaan sebelum parameter tipe wajib. Compiler menolak urutan tersebut karena cara mengisi parameter wajib menjadi tidak dapat ditentukan, sehingga deklarasi harus diurutkan ulang dengan parameter wajib terlebih dahulu. Kekeliruan ketiga adalah memberikan batasan yang terlalu spesifik, misalnya membatasi pada Siswa padahal fungsi hanya membaca properti nama, sehingga tipe lain yang juga memiliki nama ikut tertolak. Batasan yang tepat adalah bentuk paling kecil yang propertinya benar-benar digunakan.

Ringkasan#

  • Klausa extends pada parameter tipe menyatakan syarat minimum argumen dan membuka akses properti yang dijanjikannya di dalam badan fungsi.
  • Batasan K extends keyof T membatasi kunci pada properti yang ada, dengan tipe kembalian T[K] yang mengikuti kuncinya.
  • Nilai bawaan parameter tipe diisi otomatis apabila pemanggil tidak menyebutnya, dan harus diletakkan setelah parameter tipe wajib.
  • Batasan memindahkan pemeriksaan tipe dari badan fungsi ke titik pemanggilan, sehingga error lebih dekat dengan penyebabnya.

Referensi#