Generics di TypeScript: Kode yang Dipakai Ulang dan Tetap Aman-Tipe

Panduan generics di TypeScript untuk pemula. Pelajari bagaimana parameter tipe membuat satu fungsi atau wadah bekerja untuk banyak tipe tanpa kehilangan keamanan tipe.

Diterbitkan 20 September 20269 menit bacaOleh ACY Partner Indonesia
Sampul Generics di TypeScript dengan chip kode menampilkan fungsi generic
300 × 250Slot Iklan TersediaPasang iklan Anda di sini

Bayangkan kamu menulis sebuah fungsi kecil yang menerima sebuah nilai lalu mengembalikannya begitu saja. Kalau kamu tulis khusus untuk angka, fungsi itu cuma bisa dipakai untuk angka. Mau dipakai untuk teks? Kamu harus menulisnya lagi. Lakukan itu untuk setiap tipe, dan kode kamu jadi penuh dengan salinan fungsi yang sebenarnya melakukan hal yang sama persis. Pasti ada cara yang lebih baik — dan memang ada. Namanya generics.

Bagi banyak orang, generics adalah titik di mana TypeScript mulai terasa asing dan sedikit menakutkan. Tanda kurung siku, huruf T yang berdiri sendiri, kata extends di tempat yang tak terduga — semuanya terlihat seperti bahasa lain. Padahal ide di baliknya tenang dan sederhana. Begitu kamu paham, generics akan terasa wajar dipakai, dan banyak bagian TypeScript yang tadinya membingungkan tiba-tiba jadi masuk akal. Mari kita bahas pelan-pelan dan bangun dari nol.

Masalah yang dipecahkan generics

Kita mulai dari contoh paling sederhana: fungsi yang mengembalikan apa pun yang kamu berikan. Kita namai identity, karena ia hanya menyerahkan kembali nilainya tanpa diubah.

Ini percobaan pertama, khusus untuk angka:

function identityNumber(value: number): number {
  return value;
}

Berfungsi, tapi cuma untuk angka. Begitu kamu butuh untuk teks, kamu menulis satu lagi:

function identityString(value: string): string {
  return value;
}

Kamu pasti sudah bisa menebak arahnya. Versi untuk boolean, untuk array, untuk tipe User buatanmu sendiri — daftarnya tidak ada habisnya. Mungkin lalu kamu tergoda memakai any, tipe yang mematikan pengecekan tipe:

function identityAny(value: any): any {
  return value;
}

Kode ini lolos kompilasi dan menerima apa saja. Tapi ia juga membuang satu-satunya alasan kamu memakai TypeScript. Saat kamu memanggil identityAny(42), hasilnya bertipe any, jadi TypeScript tidak lagi tahu bahwa itu angka. Kamu kehilangan autocomplete, kehilangan pengecekan error, dan salah ketik bisa lolos tanpa ketahuan. Kamu menukar keamanan demi kelenturan — dan itu pertukaran yang merugikan.

Yang sebenarnya kamu mau adalah satu fungsi yang lentur sekaligus aman. Fungsi yang berkata: “Aku akan bekerja dengan tipe apa pun yang kamu berikan, dan aku jamin mengembalikan tipe yang sama.” Janji itulah yang diungkapkan oleh sebuah generic.

Generic pertamamu: parameter tipe

Perhatikan versi ini:

function identity<T>(value: T): T {
  return value;
}

Ada satu hal baru: <T>. Itu adalah parameter tipe. Sama seperti parameter biasa (value) adalah tempat kosong untuk sebuah nilai yang kamu isi saat memanggil fungsi, parameter tipe <T> adalah tempat kosong untuk sebuah tipe yang akan kamu isi.

T cuma sebuah nama. Ini konvensi — T dari “Type” — tapi kamu bebas menamainya apa saja: <Item>, <Value>, <Thing>. TypeScript tidak peduli pada namanya; yang penting kamu memakai nama yang sama di tiga tempat:

function identity<T>(value: T): T
                  │        │     │
                  │        │     └─ tipe kembalian: T yang sama
                  │        └─────── tipe parameter: T
                  └──────────────── deklarasi tempat kosong T

Bacalah seperti satu kalimat: “Untuk suatu tipe T, fungsi ini menerima value bertipe T dan mengembalikan nilai bertipe T.” Fungsinya tidak tahu dan tidak peduli T itu tipe apa — tapi ia menjamin tipe yang masuk sama dengan tipe yang keluar.

Sekarang lihat apa yang terjadi saat dipanggil:

const a = identity<string>("hello"); // a bertipe string
const b = identity<number>(42);      // b bertipe number

Saat kamu menulis identity<string>, kamu sedang mengisi tempat kosongnya: “kali ini, T adalah string.” Jadi value harus berupa string, dan tipe kembaliannya juga string. Tidak ada any di mana pun, keamanan tipe penuh, satu fungsi saja.

Biasanya kamu tidak perlu menulis tipenya

Di kebanyakan kasus kamu bisa melepas <string> dan cukup memanggil identity("hello"). TypeScript melihat argumennya, tahu itu string, lalu menyimpulkan sendiri bahwa T adalah string. Ini disebut inferensi tipe, dan inilah yang membuat generics terasa ringan dalam kode sehari-hari.

Jadi versi eksplisit identity<number>(42) dan versi singkat identity(42) artinya sama. Tulis tipenya secara jelas kalau TypeScript tidak bisa menebak, atau kalau kamu ingin benar-benar gamblang; selebihnya biarkan inferensi yang bekerja.

Wadah yang generic

Satu fungsi adalah awal yang lembut. Generics baru benar-benar terasa gunanya saat kamu membangun sebuah wadah — sesuatu yang menyimpan nilai bertipe tertentu lalu mengembalikannya nanti. Bayangkan kotak berlabel: kotaknya selalu kotak, tapi isinya bisa apa saja, dan labelnya memberi tahu persis apa yang ada di dalam.

interface Box<T> {
  value: T;
}

const stringBox: Box<string> = { value: "hello" };
const numberBox: Box<number> = { value: 42 };

Box<T> adalah interface generic. Ia menggambarkan bentuk sebuah objek dengan satu properti, value, yang tipenya adalah T apa pun yang kamu pilih. Saat kamu menulis Box<string>, kamu dapat kotak yang value-nya harus string. Box<number> memberimu kotak yang berisi angka. Satu definisi, dipakai ulang tanpa batas — dan setiap kotak tetap ketat soal apa yang dibawanya.

Manfaatnya kelihatan saat kamu memakai isinya:

console.log(stringBox.value.toUpperCase()); // Oke: value berupa string
console.log(numberBox.value.toFixed(2));    // Oke: value berupa angka

TypeScript tahu stringBox.value adalah string, jadi ia menawarkan metode string seperti toUpperCase. Ia tahu numberBox.value adalah angka, jadi toFixed tersedia. Coba tulis numberBox.value.toUpperCase() dan TypeScript langsung menghentikanmu bahkan sebelum kode dijalankan, karena angka tidak punya metode itu. Itulah inti janji generics: lentur tanpa kehilangan setitik pun keamanan.

Kamu bisa melakukan hal yang sama dengan alias type, dan bacaannya hampir identik:

type Pair<A, B> = {
  first: A;
  second: B;
};

const coordinate: Pair<number, number> = { first: 10, second: 20 };
const labeled: Pair<string, boolean> = { first: "active", second: true };

Ya — kamu boleh punya lebih dari satu parameter tipe. Pair<A, B> punya dua, dan kamu mengisi keduanya. Beginilah cara tipe bawaan yang sudah sering kamu pakai dibangun. Array berisi string sebenarnya adalah Array<string>, dan promise yang menghasilkan User adalah Promise<User>. Tanpa sadar, kamu sudah memakai generics sejak lama.

Constraint: membatasi T

Sampai sini T boleh berupa tipe apa pun. Itu kuat, tapi kadang terlalu longgar. Misalkan kamu ingin fungsi yang membaca length dari apa pun yang kamu berikan:

function logLength<T>(item: T): T {
  console.log(item.length); // Error: 'length' tidak ada pada tipe 'T'
  return item;
}

TypeScript menolaknya, dan itu tepat. Karena T bisa apa saja — angka, boolean, tipe buatanmu — tidak ada jaminan ia punya properti length. Angka tidak punya. Kamu menjanjikan kelenturan yang terlalu lebar, jadi kamu tidak bisa menyentuh .length dengan aman.

Solusinya adalah constraint (batasan). Kamu memakai kata kunci extends untuk berkata: “T tetap boleh banyak tipe, tapi minimal ia harus punya properti ini.” Di sini kita mewajibkan T punya length berupa angka:

function logLength<T extends { length: number }>(item: T): T {
  console.log(item.length); // Sekarang oke
  return item;
}

logLength("hello");        // Oke: string punya length
logLength([1, 2, 3]);      // Oke: array punya length
logLength({ length: 10 }); // Oke: objek ini punya length
logLength(42);             // Error: angka tidak punya length

Baca T extends { length: number } sebagai “T harus berupa tipe yang setidaknya memuat properti length: number.” String memenuhi syarat, array memenuhi syarat, dan objek apa pun yang punya length berupa angka juga lolos. Angka biasa tidak, jadi TypeScript memblokir pemanggilan itu saat kompilasi.

extends di sini berarti 'minimal berbentuk', bukan 'mewarisi'

Kalau kamu pernah melihat extends pada class, artinya pewarisan. Pada constraint generic, maknanya berkaitan tapi berbeda: “bisa ditugaskan ke” atau “minimal berbentuk seperti ini.” T extends { length: number } tidak berarti T adalah turunan dari sesuatu — artinya apa pun T nanti, ia harus cocok dengan bentuk itu.

Constraint inilah yang membuat generics benar-benar berguna di kode nyata. Ia membuatmu tetap lentur soal tipe, sambil tetap bisa mengandalkan bagian spesifik yang memang kamu butuhkan.

Contoh praktis: pembungkus bertipe

Mari kita rangkai dengan sesuatu yang lebih dekat ke pekerjaan nyata — sebuah pembungkus hasil kecil, semacam yang mungkin kamu pakai untuk membawa data lewat aplikasi beserta penanda statusnya.

interface Result<T> {
  success: boolean;
  data: T;
}

interface User {
  id: number;
  name: string;
  email: string;
}

function wrap<T>(data: T): Result<T> {
  return { success: true, data };
}

const userResult = wrap<User>({
  id: 1,
  name: "Jane Doe",
  email: "jane@example.com",
});

console.log(userResult.data.name);  // Oke: data berupa User
console.log(userResult.success);    // boolean

Result<T> adalah interface generic, dan wrap adalah fungsi generic yang membentuknya. Karena parameter tipe mengalir dari argumen sampai ke tipe kembalian, userResult.data dikenali sebagai User, lengkap dengan name, email, dan id yang semuanya tersedia dan terperiksa. Berikan sebuah produk atau pesanan, dan wrap yang sama menyesuaikan diri tanpa perubahan sedikit pun. Satu potong kode kecil, benar untuk setiap tipe yang kamu berikan.

Kapan sebaiknya memakai generics

Generics adalah alat, bukan tujuan. Patokannya sederhana: pakai generic kalau logika yang sama harus bekerja untuk banyak tipe, dan hubungan antara tipe masukan dan tipe keluaran itu penting.

Situasi Cocok pakai generic?
Logika yang sama berlaku untuk banyak tipe Ya
Tipe masukan menentukan tipe keluaran Ya
Wadah menyimpan nilai dengan tipe berbeda-beda Ya
Tipenya selalu satu tipe tetap yang sama Tidak — pakai tipe itu langsung
Cuma butuh “objek apa pun”, bentuk tak diketahui Sering kali unknown, bukan generic

Kalau sebuah fungsi cuma pernah menangani User, jangan dibuat generic — sebut tipenya langsung supaya lebih mudah dibaca. Generics bersinar saat pemakaian ulangnya nyata dan hubungan tipenya layak dijaga. Memaksakannya di mana-mana justru membuat kode lebih sulit dibaca tanpa manfaat apa pun.

Rangkuman

Generics membuatmu menulis satu potong kode yang bekerja untuk banyak tipe tanpa kehilangan keamanan tipe — tanpa any, tanpa fungsi kembar. Ini poin yang perlu kamu bawa:

  • Parameter tipe seperti <T> adalah tempat kosong untuk sebuah tipe, yang diisi saat fungsi atau tipenya dipakai.
  • identity<T>(value: T): T yang klasik menjamin tipe yang masuk sama dengan tipe yang keluar.
  • Interface dan type generic (Box<T>, Pair<A, B>, Result<T>) menggambarkan wadah yang menyimpan nilai bertipe apa pun, dan kamu boleh punya lebih dari satu parameter.
  • Inferensi tipe biasanya membuatmu tak perlu menulis tipenya secara eksplisit — TypeScript menyimpulkan T dari argumenmu.
  • Constraint dengan extends membuatmu bisa mewajibkan T punya properti tertentu, sehingga kamu bisa memakainya dengan aman.

Lompatan dari “fungsi ini bekerja untuk satu tipe” ke “fungsi ini bekerja untuk tipe apa pun, dengan aman” adalah inti dari generics. Ambil contoh identity dan Box, ketik sendiri, lalu amati bagaimana tipenya mengalir. Begitu aliran itu terasa wajar, sisa mesin generic TypeScript — dan sebagian besar kode di dunia nyata — akan langsung terbuka di hadapanmu.

Tag:typescriptgenericskeamanan-tipefrontendtipe
728 × 90Slot Iklan TersediaPasang iklan Anda di sini

Artikel Terkait

Lihat Semua Artikel

Artikel yang Mungkin Kamu Suka

Apa Itu Framework Frontend? — chip kode dengan tag komponen
Frontend / Dasar

Apa Itu Framework Frontend?

Penjelasan ramah untuk pemula tentang apa itu framework frontend, kenapa mereka ada, masalah apa yang mereka selesaikan dibanding JavaScript biasa, dan kapan kamu memang sebaiknya memakainya.

20 Sep 20268 menit baca
Sampul fundamentals frontend bertuliskan Apa Itu Frontend dan HTML + CSS + JS
Frontend / Dasar

Apa Itu Frontend Development?

Panduan ramah pemula tentang frontend development: bagian website yang kamu lihat dan klik secara langsung. Pahami cara kerja HTML, CSS, dan JavaScript, serta bedanya frontend dengan backend.

20 Sep 20268 menit baca
Ilustrasi sampul untuk Apa yang Dikerjakan Frontend Developer
Frontend / Dasar

Apa yang Dikerjakan Frontend Developer: Panduan untuk Pemula

Penjelasan ramah pemula tentang apa yang sebenarnya dikerjakan frontend developer setiap hari: mengubah desain jadi antarmuka yang berfungsi, membangun UI yang bisa dipakai ulang, sampai urusan responsif, aksesibilitas, dan kecepatan.

20 Sep 20269 menit baca
Kartu judul bertuliskan Styling dan Interaktivitas di atas latar biru gelap ACY Partner
Frontend / Dasar

Cara Kerja Styling dan Interaktivitas

Penjelasan ramah pemula soal bagaimana CSS menyulap HTML polos jadi tampilan rapi, dan bagaimana JavaScript menambahkan perilaku — pola berpikir struktur, gaya, perilaku saat membangun halaman web.

20 Sep 20268 menit baca
Tiga lapisan front-end menyatu di satu halaman web
Frontend / Dasar

Cara HTML, CSS, dan JavaScript Bekerja Bersama

Panduan praktis untuk pemula: bikin satu tombol kecil, beri struktur dengan HTML, tampilan dengan CSS, dan perilaku dengan JavaScript, lalu lihat ketiga lapisan ini bekerja sama di halaman nyata.

20 Sep 20268 menit baca
Sampul ikhtisar praktik terbaik frontend
Frontend / Dasar

Ikhtisar Praktik Terbaik Frontend

Peta ringkas tentang apa itu frontend yang baik — HTML bermakna, CSS rapi, JavaScript secukupnya, desain responsif, performa, dan progressive enhancement, lengkap dengan arahan untuk mendalaminya.

20 Sep 20268 menit baca