Kenalan sama Generics di TypeScript Biar Type Safety-mu Nggak Setengah Hati

Kenalan sama Generics di TypeScript Biar Type Safety-mu Nggak Setengah Hati
Photo by Brett Jordan/Unsplash

Sebagai tim marketing dari Javapixa Creative Studio, kami sering melihat bahwa semangat type safety di TypeScript itu seperti janji yang sering dilanggar. Banyak developer yang sudah pakai TypeScript tapi masih terperangkap di lubang any, terutama saat harus membuat fungsi yang fleksibel. Mereka ingin kode yang kuat, tapi ketika berhadapan dengan fungsi yang harus menerima berbagai macam tipe data, jurus pamungkas yang dipakai justru any, yang secara efektif mematikan kekuatan utama dari TypeScript itu sendiri.

Nah, kalau kamu ingin kode yang benar-benar solid, fleksibel, reusable, dan future-proof, sudah saatnya kamu kenalan lebih dalam dan akrab dengan Generics. Ini bukan sekadar fitur tambahan; ini adalah fondasi yang membedakan kode TypeScript biasa dengan kode yang benar-benar berkualitas tinggi, seperti yang kami kembangkan di Javapixa untuk klien-klien kami.

Artikel ini akan membawa kamu menjelajahi Generics di TypeScript dengan gaya yang santai, aplikatif, dan pastinya, langsung ke inti permasalahan.

*

Kenalan sama Generics: Pahlawan Fleksibilitas Tanpa Kompromi Type Safety

Coba bayangkan ini: Kamu sedang membuat sebuah kontainer penyimpanan serbaguna. Kontainer ini harus bisa menyimpan apa saja—mulai dari apel, kunci mobil, sampai dokumen penting—tapi begitu kamu memasukkan isinya, kamu ingin kontainer itu "mengingat" tipe barang apa yang ada di dalamnya. Kamu nggak mau setelah kamu ambil isinya, sistem malah bilang "Ini bisa jadi apa saja," karena itu sama saja dengan nggak tahu apa-apa.

Generics adalah kontainer serbaguna itu dalam dunia kode.

Secara definisi, Generics adalah alat untuk membangun komponen yang bisa bekerja dengan berbagai tipe data alih-alih hanya satu tipe saja. Fitur ini memungkinkan kamu menulis kode yang fleksibel tanpa mengorbankan pemeriksaan tipe yang ketat (type checking) dari TypeScript.

Intinya, Generics memungkinkan kita menggunakan placeholder untuk tipe data. Placeholder ini baru akan ‘diisi’ atau ‘ditentukan’ saat fungsi, interface, atau class tersebut benar-benar dipanggil atau digunakan.

Masalah Klasik: Kenapa Harus Hindari any?

Sebelum kita melangkah lebih jauh, mari kita lihat masalah yang coba dipecahkan Generics.

Misalnya, kamu ingin membuat fungsi sederhana yang mengambil satu nilai dan mengembalikannya apa adanya (sering disebut identity function):

typescript
function identifyFunction(arg: any): any {
  return arg;
}let hasil = identifyFunction("Javapixa"); 
// Hasil: tipe datanya 'any'. Kita kehilangan informasi bahwa ini adalah 'string'.hasil.toUpperCase(); // Bisa jalan

Menggunakan any membuat TypeScript buta. Ketika kamu menggunakan fungsi tersebut, TypeScript tidak tahu tipe data apa yang akan kembali, sehingga semua fitur autocompletion dan perlindungan tipe hilang.

Inilah saatnya Generics masuk, biasanya diwakili dengan huruf T (singkatan dari Type) atau huruf kapital lainnya.

*

Generics di Fungsi: Dasar-Dasar yang Harus Dikuasai

Generics paling sering digunakan pada fungsi. Sintaksnya cukup mudah: kita mendefinisikan placeholder tipe di antara kurung siku tepat setelah nama fungsi.

1. The Identity Function (Versi Generics)

Mari kita ubah fungsi identifyFunction tadi:

typescript
function identifyGenerics(arg: T): T {
  return arg;
}// Penggunaan 1: TypeScript secara otomatis inferens tipe
let outputString = identifyGenerics("Ini kode dari Javapixa");
// outputString sekarang tipenya adalah 'string'
console.log(outputString.length); // Aman!

Dengan , fungsi tersebut kini menjadi sangat fleksibel. Jika kamu memasukkan string, fungsi tahu akan mengembalikan string. Jika kamu memasukkan boolean, fungsi akan mengembalikan boolean. Integritas tipe terjaga 100%.

2. Menggunakan Multiple Generics (T dan U)

Terkadang, kamu butuh dua atau lebih tipe data yang independen. Kamu bisa menggunakan huruf kapital berbeda, misalnya T dan U.

Contoh paling umum adalah fungsi yang menggabungkan dua objek:

typescript
function merge(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 } as T & U;
}const user = { name: "Ria", age: 25 };
const address = { city: "Bandung", zip: 40141 };const mergedData = merge(user, address);
// Tipe mergedData adalah { name: string; age: number; } & { city: string; zip: number; }

Dengan Generics, kita menjamin bahwa objek hasil gabungan memiliki properti dari kedua objek input, menjaga type safety pada saat mengakses properti tersebut.

*

Generics di Interface dan Type Aliases: Kontrak Data yang Reusable

Generics sangat berguna saat kamu mendefinisikan struktur data, terutama untuk respons API. Dalam pengembangan aplikasi modern yang ditangani Javapixa Creative Studio, kita sering berurusan dengan respons standar dari backend yang biasanya memiliki metadata (status, pesan) dan data utama (payload).

Contoh Interface Response API

Misalnya, setiap respons API dari server kamu selalu dibungkus dalam format yang sama, tapi tipe data di dalamnya berubah-ubah tergantung endpoint.

typescript
// Tanpa Generics, kita harus membuat Interface baru untuk setiap endpoint
/*
interface UserResponse {
  status: number;
  message: string;
  data: User; 
}
*/// Dengan Generics, kita hanya butuh satu Interface
interface ApiResponse {
  status: number;
  message: string;
  data: T; // Tipe data payload akan ditentukan saat implementasi
}// Contoh Penggunaan
interface User {
  id: number;
  username: string;
}interface Product {
  sku: string;
  price: number;
}// Response ketika mengambil data user (T = User)
const userApiResult: ApiResponse = {
  status: 200,
  message: "Success",
  data: [{ id: 1, username: "dev_javapixa" }]
};

Lihat betapa bersihnya kode di atas. Kita hanya perlu mendefinisikan satu ApiResponse dan menggunakannya untuk endpoint apa pun, cukup dengan mengganti tipe T. Ini adalah kunci untuk membangun struktur kode yang mudah dipelihara (maintainable) dan dapat diskalakan.

*

Generics di Classes: Membangun Komponen yang Kuat

Generics juga bisa diaplikasikan pada class untuk membuat struktur data atau utilitas yang independen dari tipe data yang disimpan.

Bayangkan kamu sedang membangun class sederhana untuk menampung sebuah antrian (Queue) item.

typescript
class DataQueue {
  private items: T[] = [];enqueue(item: T): void {
    this.items.push(item);
  }dequeue(): T | undefined {
    return this.items.shift();
  }get length(): number {
    return this.items.length;
  }
}// Membuat antrian string
const stringQueue = new DataQueue();
stringQueue.enqueue("Satu");
stringQueue.enqueue("Dua");
// stringQueue.enqueue(123); // Error! Karena sudah didefinisikan sebagai stringconst nextItem = stringQueue.dequeue(); // Tipe nextItem adalah string
console.log(nextItem.toUpperCase());

Dalam class DataQueue, semua metode (enqueue dan dequeue) otomatis mengerti dan menerapkan tipe T yang didefinisikan saat instantiation objek. Ini memastikan bahwa sekali sebuah antrian dibuat untuk tipe string, ia tidak akan pernah menerima tipe lain, mencegah bug yang terkait dengan tipe data yang salah.

*

Tips Pro: Generics Constraints dan keyof (Biar Nggak Setengah Hati)

Menggunakan saja seringkali tidak cukup. Kadang, kita perlu memberi batasan (Constraint) pada tipe T agar kita bisa melakukan operasi tertentu di dalam fungsi tersebut. Inilah yang membuat kode Generics kamu benar-benar pintar.

1. Constraints Menggunakan extends

Misalnya, kamu ingin membuat fungsi yang bisa menerima array apa saja, tapi di dalam fungsi itu, kamu ingin mengakses properti .length.

Jika kamu hanya menggunakan T, TypeScript tidak tahu apakah T memiliki properti length:

typescript
/ Fungsi ini akan error jika T tidak di-constraint /
function logLength(arg: T): T {
  // console.log(arg.length); // ERROR! TS tidak tahu kalau T punya .length
  return arg;
}

Solusinya? Kita batasi T agar ia harus extends (memperluas) tipe yang memiliki properti length, misalnya sebuah array atau sebuah objek yang memiliki length. Untuk kasus paling sederhana, kita batasi dengan objek:

typescript
interface Lengthy {
  length: number;
}function logLength(arg: T): T {
  console.log(Panjang data ini adalah: ${arg.length}); 
  return arg;
}// Contoh yang valid (T extends Lengthy)
logLength("Halo Javapixa"); // Tipe String memiliki properti length
logLength([1, 2, 3]);       // Tipe Array memiliki properti length

Dengan T extends Lengthy, kita memberitahu TypeScript, "Hei, tipe T ini bisa apa saja, asalkan dia punya properti length yang bertipe number." Ini adalah mekanisme penting untuk membuat Generics yang kuat dan dapat diandalkan.

2. Mengakses Properti Objek dengan Aman menggunakan keyof

Ini adalah trik yang sangat powerful, terutama saat kamu berurusan dengan objek dinamis dan ingin mengambil nilai properti secara aman.

Bayangkan kamu ingin membuat fungsi yang mengambil objek dan nama properti, lalu mengembalikan nilai properti tersebut.

typescript
function getProperty(obj: T, key: K) {
  return obj[key];
}const person = { 
  nama: "Dira", 
  umur: 30, 
  profesi: "Developer" 
};// K di-inferens sebagai 'nama' | 'umur' | 'profesi' (literal union type)
const namaKu = getProperty(person, 'nama'); // Tipe: string
const umurKu = getProperty(person, 'umur'); // Tipe: number

K extends keyof T artinya: Tipe K harus merupakan salah satu kunci yang valid (properti) dari tipe T. Ini adalah cara terbaik untuk memastikan runtime error (salah ketik properti) ditangkap saat kompilasi. Ini adalah standar kualitas yang selalu kami terapkan di Javapixa Creative Studio.

*

Utility Types: Generics yang Sudah Disediakan TypeScript

Selain membuat Generics sendiri, TypeScript juga menyediakan banyak Utility Types bawaan yang sebenarnya adalah Generics yang sudah dibuatkan untuk mempermudah hidup kita. Menguasai ini adalah langkah cepat menuju kode yang lebih rapi.

Beberapa yang paling populer:

  1. Partial: Membuat semua properti dalam tipe T menjadi opsional.
typescript
    interface Settings {
      theme: string;
      fontSize: number;
    }
    type OptionalSettings = Partial; 
    // { theme?: string; fontSize?: number; }
  1. Readonly: Membuat semua properti dalam tipe T menjadi read-only.
typescript
    type ImmutableSettings = Readonly;
    // { readonly theme: string; readonly fontSize: number; }
  1. Pick: Memilih subset properti dari tipe T yang ditentukan oleh kunci K.
typescript
    type Profile = Pick; 
    // { theme: string; }
  1. Omit: Menghilangkan (mengeluarkan) subset properti dari tipe T yang ditentukan oleh kunci K.
typescript
    type MinimalSettings = Omit; 
    // { theme: string; }

Semua utility types ini beroperasi menggunakan prinsip Generics yang sama, memungkinkan kamu memanipulasi tipe data yang sudah ada tanpa perlu mendefinisikan ulang semuanya.

*

Kesimpulan: Jangan Takut sama Generics, Itu Bikin Kode Makin Keren

Generics di TypeScript mungkin terlihat sedikit rumit di awal karena menggunakan sintaks tambahan (, extends, keyof). Namun, begitu kamu menguasainya, kamu akan menyadari betapa pentingnya fitur ini untuk mencapai type safety yang maksimal sambil mempertahankan fleksibilitas yang dibutuhkan dalam pengembangan modern.

Kode yang ditulis dengan Generics yang baik adalah kode yang:

  1. Sangat Reusable: Fungsi atau interface bisa dipakai di banyak tempat dengan tipe data yang berbeda.
  2. Type-Safe: Risiko bug di runtime karena salah tipe data hampir nol.
  3. Self-Documenting: Tipe data secara eksplisit didefinisikan, memudahkan developer lain memahami fungsi tersebut.

Di Javapixa Creative Studio, kami selalu memastikan bahwa setiap baris kode TypeScript yang kami tulis—baik itu untuk pengembangan jasa website maupun aplikasi—memanfaatkan kekuatan penuh dari fitur seperti Generics. Ini bukan hanya masalah menulis kode yang bekerja, tetapi menulis kode yang berkualitas, terstruktur, dan siap menghadapi perubahan di masa depan.

Jika kamu sedang membangun proyek besar yang membutuhkan fondasi kode yang kuat, terstruktur, dan highly maintainable, atau jika kamu membutuhkan tim yang ahli dalam membangun aplikasi dengan standar TypeScript profesional tertinggi, Generics adalah salah satu indikator kualitas kami. Jangan ragu untuk hubungi Javapixa Creative Studio. Kami memastikan type safety di proyekmu tidak akan pernah "setengah hati."