Generics: Fungsi yang Fleksibel tapi Tetap Aman

Memahami masalah yang dipecahkan generics, menulis fungsi generik dengan parameter tipe <T>, inference otomatis, lebih dari satu parameter tipe, dan generics pada array.

Part 13 dari 24 Β± 50 menit Gratis

Masalahnya

Bayangkan fungsi yang mengembalikan elemen pertama sebuah array. Untuk array angka ia mengembalikan angka, untuk array teks mengembalikan teks. Bagaimana menuliskan tipenya?

function pertamaAngka(arr: number[]): number | undefined { return arr[0] }
function pertamaTeks(arr: string[]): string | undefined { return arr[0] }
// ...mengulang untuk setiap tipe? Melelahkan.

function pertamaAny(arr: any[]): any { return arr[0] }
const x = pertamaAny([1, 2, 3])   // x: any β€” tipe hilang, proteksi hilang 😞

Kita ingin: "apa pun tipe isi array-nya, kembalikan tipe yang sama". Itulah tugas generics.

Parameter tipe <T>

Generics memperkenalkan variabel untuk tipe, ditulis dalam kurung siku < >. Anggap T sebagai "tipe apa pun yang nanti ditentukan saat fungsi dipakai":

function pertama<T>(arr: T[]): T | undefined {
  return arr[0]
}

const a = pertama([1, 2, 3])          // T = number  β†’ a: number | undefined
const b = pertama(["x", "y"])         // T = string  β†’ b: string | undefined
const c = pertama([true, false])      // T = boolean

Kamu tidak perlu menulis <number> saat memanggil: TypeScript menyimpulkan T dari argumen. Boleh juga menulisnya eksplisit: pertama<number>([1, 2]) β€” berguna saat inference tidak cukup (mis. array kosong).

Analogi

Generics seperti kotak dengan label yang bisa diganti. Kotaknya sama (logikanya sama), tetapi label "isi: T" diganti sesuai barang yang dimasukkan. Fungsi pertama adalah satu kotak untuk semua jenis barang, dan tipe barang yang keluar selalu sama dengan yang masuk.

Contoh klasik: identity

function identitas<T>(nilai: T): T {
  return nilai
}
identitas("halo")   // tipe kembalian: "halo" (string literal)
identitas(42)       // 42

Lebih dari satu parameter tipe

Beri nama yang berurutan atau bermakna: T, U, K, V, atau nama deskriptif.

function pasangkan<A, B>(a: A, b: B): [A, B] {
  return [a, b]
}
const p = pasangkan("umur", 25)      // [string, number]

function petakan<Masuk, Keluar>(daftar: Masuk[], fn: (x: Masuk) => Keluar): Keluar[] {
  const hasil: Keluar[] = []
  for (const x of daftar) hasil.push(fn(x))
  return hasil
}
const panjang = petakan(["a", "bb", "ccc"], (s) => s.length)   // number[]

Generics di tipe bawaan

Kamu sebenarnya sudah memakai generics tanpa sadar:

TipeArtinya
Array<string>Sama dengan string[]
Promise<number>Janji yang akan menghasilkan number (bab 21)
Map<string, number>Peta dengan kunci string dan nilai number
Set<string>Himpunan berisi string
Record<string, boolean>Object berkunci string bernilai boolean
const peta = new Map<string, number>()
peta.set("apel", 3)
peta.set("jeruk", "lima")   // ❌ Argument of type 'string' is not assignable to parameter of type 'number'

Generics di type alias dan interface

type Kotak<T> = { isi: T }
const kotakAngka: Kotak<number> = { isi: 7 }
const kotakTeks: Kotak<string> = { isi: "halo" }

interface Hasil<T> {
  data: T
  waktu: number
}

Kapan memakai generics?

  • Saat logikanya sama untuk banyak tipe, dan hubungan antara tipe masukan dan keluaran perlu dijaga.
  • Kalau tipenya hanya dipakai sekali di tanda tangan fungsi, biasanya generics tidak perlu: function cetak(x: string[]) lebih sederhana daripada cetak<T extends string>(x: T[]).

Kesalahan umum pemula

  • Memakai any padahal generics bisa mempertahankan tipe.
  • Membuat parameter tipe yang tidak dipakai di mana pun selain satu tempat (tanpa hubungan antar tipe).
  • Memakai nama tipe satu huruf tanpa makna untuk situasi rumit β€” gunakan nama deskriptif seperti TItem.

Praktikkan langsung, gratis

Tulis dan jalankan kode langsung di browser, dapatkan penilaian otomatis. Daftar gratis kapan saja untuk menyimpan progres dan meraih sertifikat.

Coba latihan sekarang, tanpa daftar
Bagikan: WhatsApp Facebook LinkedIn X