genericdefaultMenengah3 mnt baca

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>:

typescript
// 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:

typescript
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):

typescript
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 =:

typescript
// 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:

typescript
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. unknown sering lebih tepat daripada any sebagai 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.

typescript
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 };