Default Type Parameter
Nilai default untuk parameter generic: <T = string> agar pemakaian lebih ringkas.
Nilai Default untuk Parameter Tipe
Kamu sudah kenal default pada parameter fungsi: function sapa(nama = "tamu"). Generic punya padanannya: <T = string>. Artinya: "kalau pemakai tidak menentukan tipe, pakai ini". Konsepnya sederhana, tapi dampaknya besar untuk kenyamanan API dan kompatibilitas kode lama.
Kasus Klasik: Evolusi Tanpa Merusak
Bayangkan interface Kotak sudah dipakai di 200 file dengan isi string. Suatu hari kamu butuh varian number. Tanpa default, kamu punya dua pilihan buruk: duplikasi interface, atau memaksa 200 file menambahkan <string>:
// SEBELUM: dipakai di mana-mana
interface Kotak { isi: string; }
// SESUDAH dengan default: kode lama tetap jalan tanpa diubah
interface Kotak<T = string> {
isi: T;
}
const lama: Kotak = { isi: "tetap string" }; // T = string via default
const baru: Kotak<number> = { isi: 42 }; // override eksplisit
const eksplisit: Kotak<string> = { isi: "sama saja" };Inilah alasan utama default ada: menambah generic ke tipe yang sudah mapan tanpa breaking change. Library besar melakukan ini terus-menerus saat API-nya berevolusi.
Skenario Nyata: 90 Persen Kasus Sama
Banyak utilitas punya "tipe umum" yang dipakai hampir selalu, dengan segelintir kasus khusus. Contoh: wrapper response yang 90 persen mengembalikan object JSON, tapi kadang teks mentah:
interface Hasil<T = Record<string, unknown>> {
ok: boolean;
data: T;
waktuMs: number;
}
async function ambil<T = Record<string, unknown>>(url: string): Promise<Hasil<T>> {
const t0 = Date.now();
const res = await fetch(url);
const data = (await res.json()) as T;
return { ok: res.ok, data, waktuMs: Date.now() - t0 };
}
const umum = await ambil("/api/profil"); // Hasil<Record<string, unknown>>
const khusus = await ambil<string[]>("/api/tags"); // Hasil<string[]>Pemakai kasus umum tidak perlu menulis apa-apa; pemakai kasus khusus tinggal override. Tanpa default, setiap pemanggilan wajib menulis argumen tipe, dan API terasa cerewet.
Contoh Error Nyata: Urutan Parameter
Ada dua aturan urutan yang kalau dilanggar langsung error. Pertama, parameter wajib tidak boleh mengikuti parameter opsional (sama seperti parameter fungsi):
interface Salah<T = string, U> { a: T; b: U; }
// Error: Required type parameters may not follow optional type parameters.Perbaikannya: beri default ke U juga, atau pindahkan U ke depan. Kedua, urutan penulisan constraint vs default: extends dulu, baru =:
// Benar: constraint dulu, default kemudian
interface Cache<T extends { id: string } = { id: string; nama: string }> {
simpan(item: T): void;
}
const c1: Cache = { simpan: (i) => console.log(i.nama) }; // default dipakai
const c2: Cache<{ id: string; sku: string }> = {
simpan: (i) => console.log(i.sku),
};Perhatikan c1: default-nya sendiri harus memenuhi constraint-nya. Kalau default melanggar extends, error muncul di definisi interface, bukan di pemakaian.
Default Bukan Fallback Inference
Kesalahpahaman umum: "kalau inference gagal menebak T, default yang dipakai". Salah. Default hanya dipakai bila argumen tipe tidak ditulis SAMA SEKALI:
interface Respons<T = string> { data: T; }
const a: Respons = { data: "x" }; // T = string (default)
const b: Respons<unknown> = { data: 1 }; // T = unknown, BUKAN string!
const c = { data: "x" } as Respons; // T = string (default)
function bungkus<T = number>(x?: T): T[] {
return x === undefined ? [] : [x];
}
const d = bungkus(); // T = number? BUKAN, T = unknown!Kasus d mengejutkan banyak orang: karena argumen tipe tidak ditulis, orang mengira default number dipakai. Nyatanya untuk pemanggilan fungsi, inference berjalan dulu dan menghasilkan unknown; default generic hanya berlaku untuk posisi tipe (seperti Respons), bukan untuk inference pemanggilan fungsi. Kalau butuh default pada fungsi, tulis eksplisit: bungkus<number>().
Catatan teknis: Default yang baik adalah tipe yang paling umum DAN paling aman.
unknownsering lebih tepat daripadaanysebagai default karena memaksa pemakai melakukan pengecekan sebelum memakai nilainya, alih-alih diam-diam membiarkan operasi berbahaya.
Tantangan
Paginasi dengan default
Buat interface Halaman<T = unknown> dengan data: T[], total: number, halaman: number. Buat dua variabel: satu tanpa argumen tipe (data unknown[]), satu dengan Halaman<string>. Pastikan keduanya lolos compiler.
interface Halaman<T = unknown> {
// tulis di sini
}
const a: Halaman = { data: [1, "x"], total: 2, halaman: 1 };
const b: Halaman<string> = { data: ["a", "b"], total: 2, halaman: 1 };