Capek Sama `any`? Ini Cara Kita Bikin Type Generic Components Paling Rapi di TypeScript

Capek Sama `any`? Ini Cara Kita Bikin Type Generic Components Paling Rapi di TypeScript
Photo by Patrick Martin/Unsplash

Gini nih, kalau kita ngomongin dunia development modern, khususnya yang pakai framework kayak React, Vue, atau Angular, pasti kita nggak bisa lari dari yang namanya TypeScript. Kenapa? Karena TS itu penjaga gawang andalan kita biar codebase nggak amburadul dan gampang di-maintain.

Tapi ada satu hantu yang sering banget nongol, apalagi kalau lagi buru-buru atau mentok: si any. Jujur aja, siapa sih yang nggak pernah pakai any? Rasanya kayak jalan pintas yang manis, tapi ujung-ujungnya bikin kita tersesat di hutan type checking yang gelap. Kita tahu, menggunakan any di komponen generik adalah dosa besar dalam dunia type safety. Komponen yang harusnya fleksibel malah jadi unpredictable.

Di Javapixa Creative Studio, kami percaya bahwa kualitas code itu mencerminkan kualitas produk yang kami berikan ke klien. Dan kualitas itu dimulai dari typing yang rapi. Kalau kamu sering bikin komponen yang harus bisa menerima berbagai jenis data (misalnya, komponen Table, List, atau Dropdown yang bisa dipakai untuk data User, data Produk, atau data Apapun), kamu wajib banget kenalan sama Type Generic Components yang benar-benar bersih.

Yuk, kita bedah tuntas, gimana caranya bikin komponen generik yang bukan cuma berfungsi, tapi juga elegan dan future-proof.

*

Kenapa Kita Harus Jauhin any (Serius, Jauhin!)

Sebelum kita masuk ke Generics, mari kita setel dulu pola pikirnya. Kenapa any itu berbahaya, terutama di konteks komponen yang seharusnya fleksibel?

  1. Menghilangkan Kepercayaan Diri TypeScript: Tujuan utama kita pakai TS adalah mendapatkan autocompletion yang cerdas dan peringatan dini saat ada kesalahan tipe. Begitu kamu pakai any, semua keuntungan itu hilang.
  2. Meningkatkan Biaya Maintenance: Semakin besar aplikasi, semakin sulit melacak data apa yang seharusnya masuk ke komponen ber-any. Kalau ada bug di produksi, kamu harus debug secara manual karena compiler TS nggak bantu sama sekali.
  3. Dokumentasi yang Buruk: Tipe itu adalah bentuk dokumentasi terbaik. Kalau tipenya any, artinya komponen itu tidak mendokumentasikan ekspektasi inputnya.

Generics (simbol yang biasa kita lihat sebagai ) adalah solusi terbaik. Generics memungkinkan kita mendefinisikan sebuah komponen atau fungsi yang beroperasi pada berbagai tipe data, tanpa kehilangan manfaat type safety.

*

Pondasi Awal: Mendefinisikan Generic Component

Di ekosistem React (yang paling sering menggunakan pattern ini), mendefinisikan komponen generik itu sedikit berbeda tergantung kamu pakai arrow function atau function declaration.

1. Cara Paling Bersih (Menggunakan Arrow Function)

Ini adalah pattern yang paling sering kami gunakan di Javapixa karena lebih eksplisit dan rapi.

Misalnya, kita mau bikin komponen GenericList yang bisa menerima array berisi tipe data apa pun:

typescript
// T adalah Type Parameter yang kita definisikan
interface ListProps {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}// Catatan penting: Untuk component React, kita perlu mendefinisikan generic
// di luar komponen itu sendiri, lalu memasukkannya ke tipe React.FC (atau sejenisnya)
// Tapi cara yang lebih clean adalah mendefinisikan function declaration seperti ini
// atau menggunakan helper (seperti yang ditunjukkan di bawah).function GenericList(props: ListProps) {
  return (
    
      {props.items.map((item, index) => (
        
          {props.renderItem(item)}
        
      ))}
    
  );
}

Ketika kamu menggunakan komponen ini, TypeScript akan langsung "mencium" tipe data yang kamu berikan, dan autocomplete akan bekerja sempurna di dalam fungsi renderItem.

Contoh Penggunaan:

typescript
interface User {
  id: number;
  name: string;
}const userList: User[] = [
  { id: 1, name: "Bambang" },
  { id: 2, name: "Susi" }
];// Di sini, TS otomatis tahu bahwa T adalah User
const App = () => (
   {
      // user.name akan muncul di autocomplete!
      return User ID: {user.id} - Nama: {user.name};
    }}
  />
);

Lihat? Tidak ada any, tapi kita tetap fleksibel.

*

Level Up: Membatasi Generics dengan extends

Seringkali, kamu nggak mau komponenmu menerima semua tipe data. Kamu mungkin butuh jaminan bahwa tipe data yang masuk setidaknya memiliki properti tertentu (misalnya, harus punya id untuk dijadikan key, atau harus punya properti title untuk ditampilkan).

Di sinilah peran extends sangat vital.

2. Enforcing Properties (Key Requirement)

Misalnya, kita mau memodifikasi GenericList tadi. Kita nggak mau pakai index sebagai key karena itu nggak optimal. Kita wajibkan setiap item memiliki properti id (dengan tipe string atau number).

typescript
// Sekarang T harus merupakan objek yang memiliki properti 'id'
interface Identifiable {
  id: string | number;
}// Kita batasi T: T harus 'meng-extend' Identifiable
interface ConstrainedListProps {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}function ConstrainedList(props: ConstrainedListProps) {
  return (
    
      {/ Sekarang kita bisa jamin item.id itu ada /}
      {props.items.map((item) => (
         
          {props.renderItem(item)}
        
      ))}
    
  );
}

Implikasi Kerennya:

  1. Kalau kamu mencoba memasukkan array dari tipe data primitif (misalnya string[] atau number[]), TypeScript akan langsung teriak: "Tipe ini tidak memiliki properti id!"
  2. Keamanan komponenmu meningkat drastis. Kamu sudah jamin bahwa logika di dalam komponen tidak akan crash karena mengakses properti yang tidak ada.

3. Menggunakan Generic dengan keyof untuk Komponen Dinamis

Ini adalah salah satu pattern favorit di Javapixa Creative Studio, terutama saat membuat komponen Data Table yang sangat fleksibel. Kita tidak hanya ingin tahu tipe datanya (T), tapi kita juga ingin tahu tipe kunci (properti) yang ingin ditampilkan.

Bayangkan kita membuat komponen SimpleViewer yang hanya menampilkan satu properti dari objek yang diberikan.

typescript
interface DataViewerProps {
  data: T; // Objek data itu sendiri
  displayKey: K; // Kunci dari objek T yang ingin ditampilkan
}function SimpleViewer(props: DataViewerProps) {
  const { data, displayKey } = props;
  
  // Mengakses properti objek secara dinamis, tapi tetap aman di TS
  const valueToDisplay = data[displayKey];return (
    
      {String(displayKey)}: {String(valueToDisplay)}
    
  );
}// Contoh Penggunaan
interface Product {
  name: string;
  price: number;
  inStock: boolean;
}const productData: Product = {
  name: "Kopi Arabika",
  price: 50000,
  inStock: true
};const App = () => (
  
    {/ TS hanya mengizinkan 'name', 'price', atau 'inStock' di displayKey /}
    
    
    
    {/ Kalau kita ketik 'color', TS akan error! /}
    {/  /} 
  
);

Dengan menggunakan dua parameter generik (T dan K extends keyof T), kita berhasil membuat komponen yang sangat dinamis, namun compiler tetap ketat mengawasi developer agar tidak salah memasukkan nama properti. Ini benar-benar menghilangkan kebutuhan untuk melakukan runtime checking yang merepotkan.

*

Tantangan Kompleks: Typing Props Komponen yang Bersifat Opsional

Dalam pengembangan user interface, seringkali komponen kita perlu merender elemen yang berbeda berdasarkan prop tertentu. Misalnya, sebuah komponen Button yang bisa berfungsi sebagai tombol () atau tautan ().Jika kamu hanya menggunakan any atau mencoba menggabungkan semua properti potensial dalam satu interface, kamu akan berakhir dengan banyak properti opsional (?) yang sebenarnya saling bertentangan.Solusinya? Conditional Types dan Discriminated Unions.Meskipun ini bukan Generics murni, ini sering digunakan bersamaan dengan Generics untuk menciptakan komponen yang paling rapi.4. Conditional Props TypingKita ingin komponen ActionElement kita menerima properti href HANYA JIKA tipe elemennya adalah 'link'.typescript
// Tipe Dasar (Common Props)
interface BaseProps {
children: React.ReactNode;
onClick?: () => void;
}// Definisi Union Type
type ActionProps = BaseProps & {
type: T;
} & (T extends 'link'
? { href: string; disabled?: never } // Jika 'link', butuh href, jangan disabled
: { href?: never; disabled?: boolean } // Jika 'button', boleh disabled, jangan href
);

// Komponen (menggunakan Generics untuk mendapatkan tipe yang benar)
function ActionElement(props: ActionProps) {
if (props.type === 'link') {
// Di sini TS tahu props pasti punya href
return
{props.children};
}

// Di sini TS tahu props mungkin punya disabled
return

{props.children}; }

// Penggunaan yang rapi dan aman: const MyComponent = () => ( <> {/ Ini adalah link. Jika kamu coba menambahkan disabled, TS akan error. /}Go to Dashboard{/ Ini adalah button. Jika kamu coba menambahkan href, TS akan error. /}Submit);

Dengan pola ini, kita tidak hanya menghindari any, tapi kita juga memberikan developer experience yang luar biasa. TypeScript bertindak seperti asisten pribadi yang tahu persis properti apa yang relevan dan dibutuhkan, berdasarkan properti lain yang sudah kita setel.

*

Standar Javapixa: Naming Convention dan Clarity

Untuk mencapai kode yang benar-benar bersih dan maintainable, khususnya di proyek besar yang dikelola oleh tim, ada beberapa best practice yang kami terapkan di Javapixa Creative Studio:

1. Naming Parameter Generik

Meskipun banyak yang menggunakan T (Type), usahakan selalu gunakan nama yang deskriptif untuk type parameter generik. Ini sangat membantu keterbacaan, terutama jika kamu menggunakan lebih dari satu generic.

  • Hindari: function MyComponent(...)
  • Gunakan: function MyComponent(...) atau function List(...)

2. Pisahkan Definition dan Implementation

Untuk komponen yang sangat kompleks, lebih baik pisahkan definisi props interface generik dari implementasi komponen itu sendiri. Ini membuat file lebih terstruktur dan mudah di-import kembali.

typescript
// types.ts
export interface TableProps {
  data: T[];
  columns: Array;
}// TableComponent.tsx
import { TableProps } from './types';

3. Selalu Dokumentasikan Batasan (Constraints)

Jika kamu menggunakan extends untuk membatasi tipe generik, pastikan kamu menambahkan komentar singkat di atas definisi interface atau komponen. Ini membantu developer baru memahami mengapa data mereka ditolak oleh compiler.

typescript
/
 * Komponen List Generik. 
 * Catatan: Setiap item (T) wajib memiliki properti 'id' (string|number).
 */
function ConstrainedList(props: ConstrainedListProps) {
  // ...
}

*

Penutup

Menggunakan Generics memang terasa sedikit lebih rumit di awal dibandingkan menempelkan any di mana-mana. Namun, investasi waktu yang kamu habiskan untuk memahami konsep T, extends, dan keyof akan terbayar lunas.

Di lingkungan profesional seperti yang kami jalankan di Javapixa Creative Studio, type safety bukan cuma masalah teknis, tapi sudah menjadi standar kualitas produk. Dengan menguasai teknik Generic Components yang rapi ini, kamu tidak hanya membuat kode yang aman dari bug, tetapi juga codebase yang elegan, mudah dikembangkan, dan siap menerima scaling di masa depan.

Jadi, sudahi hubungan toksikmu dengan any. Mari kita sambut masa depan development yang lebih bersih dan terstruktur dengan kekuatan penuh TypeScript Generics! Selamat ngoding!