Pengenalan tipe generic pada Typescript

Pengenalan tipe generic pada Typescript

Generic merupakan salah satu fitur pada typescript. Generic memungkinkan developer melakukan abstraksi terhadap tipe data dalam typescript. Tipe data generic ditandai dengan penggunaan Angle Bracket (<>) sebagai pembungkus dari suatu type.

type Generic<T> = T

Pengenalan

Konsep Don't Repeat Yourself (Dry) merupakan konsep yang penting dalam konteks penulisan kode yang dinamis dan reusable.

Dengan generic kita dapat membuat kode yang reusable pada function, class, atupun interface.

Tanpa disadari, kita mungkin sudah pernah mengimplementasikan generic pada kehidupan sehari-hari. Sebagai contoh mari lihat potongan kode berikut :

const [counter, setCounter] = useState<number>(0)

Kode diatas menginisialisasi state counter dan setter setCounter menggunakan useState hook. Dengan memberikan tipe data number secara eksplisit lewat generic.

Kebanyakan hook pada react secara default sudah menerapkan generic. Sehingga kita dapat mengetahui secara pasti tipe data variable dari hook-hook tersebut.

Contoh kasus

Terkadang kita ingin membuat sebuah function yang menerima parameter dengan tipe data yang berbeda, akan tetapi memiliki logic yang sama. Seperti contoh berikut :

// Tipe parameter number
function foo(param: number): number {
  // ...
  return param
}
 
// Tipe parameter string
function foo(param: string): string {
  // ...
  return param
}

Dalam kode diatas, kita mendefinisikan function yang sama untuk tipe data number dan tipe data string. Akan lebih baik jika kita mengabstraksi tipe data yang diterima dari function sehingga kita tidak perlu mengulang kode yang sama secara terus menerus. Dengan generic kita bisa melakukannya !

Membuat Tipe Generic

Untuk lebih memahami konsep dari generic, pertama mari kita ubah setiap tipe data function diatas menjadi any. Tipe any dalam typescript memungkinkan kita untuk melakukan assign terhadap semua type yang ada pada typescript.

function foo(param: any): any {
  // ...
  return param
}

Bagus, sekarang kita dapat melakukan assign terhadap semua tipe data. Namun apakah ada cara agar typescript dapat melakukan infer terhadap tipe data yang kita sediakan pada parameter ? Sehingga, tipe data apapun yang ada pada parameter akan digunakan juga sebagai Return Type dari function tersebut. Disinilah fitur generic akan kita terapkan.

function foo<T>(param: T): T {
  // ...
  return param
}

Pada kode diatas kita membuat function foo menjadi generic ditandai dengan penggunaan <T>, dimana type T adalah tipe data generic. Artinya, type T akan tergantung dari type yang kita spesifikasikan. Seperti contoh berikut :

function foo<T>(param: T): T {
  // ...
  return param
}
 
const a = foo<number>(2)
a //number
 
const b = foo<string>('2')
b //string

Tentu saja jika generic yang kita spesifikasikan tidak sesuai dengan tipe dari param, maka typescript akan memberikan error.

const a = foo<number>('2') // Error karena number is not assignable dengan string
 
const b = foo<string>(2) // Error karena string is not assignable dengan number

Pada function yang pertama, kita memberikan type number sebagai generic dari function foo. Sehingga tipe dari param dan tipe data yang di return oleh function akan menjadi number. Sebaliknya pada function kedua kita memberi type string, sehingga tipe param dan Return type function menjadi string.

Kamu bisa menganggap generic seperti function biasa, dapat menerima argumen (dalam hal diatas type argument) dan menggunakan argumen tersebut didalamnya. Hanya saja argumen pada generic secara khusus hanya menerima type saja.

Oke, sekarang kita punya function foo yang memiliki Return type sesuai dengan tipe yang kita spesifikasi tanpa harus melakukan duplikasi kode. Masalah kita sudah terselesaikan. Tetapi saya ingin menunjukkan satu hal yang menarik !

Mari coba hilangkan Type argument saat kita memanggil function foo dan lihat apa yang terjadi.

function foo<T>(param: T): T {
  // ...
  return param
}
 
const a = foo(2)
a // tipe data a menjadi 2
 
const b = foo('2')
b //tipe data b menjadi '2'

Tipe data dari variabel a dan b menjadi sama dengan argumen dari function foo 😮😮. Wow apa yang terjadi ?

siapa-sangka

Jadi ketika generic digunakan didalam function dan kita tidak memberikan type terhadap generic tersebut, maka typescript akan berusaha melakukan infering kepada generic pada saat runtime. Pada kasus diatas, param memiliki type T, jadi typescript akan menyimpulkan bahwa type yang dimiliki oleh param adalah sama dengan type yang dimiliki oleh T. Begitupula dengan Return type dari function tersebut. Sehingga tipe yang kita berikan pada setiap instansiasi function menjadi dinamis dan berbeda-beda sesuai dengan parameter yang kita berikan.

Satu hal lagi, pada contoh diatas kita menggunakan const pada saat mendefinisikan variabel a dan b. Karena nilai pada const adalah constant, maka typescript akan cukup cerdas saat melakukan infering. Sehingga tipe data yang didapatkan bukanlah number atau string, melainkan 2 atau '2'. Sebaliknya, jika kita menggunakan let, maka tipe yang kita dapatkan adalah string atau number, karena variabel tersebut mungkin saja berubah.

Penutup

That's it ! Ada banyak sekali hal lain yang bisa kita lakukan terhadap generic, seperti melakukan Conditional Type, Mapped Type, dll. Sayangnya kita tidak akan membahasnya pada artikel kali ini (mungkin di artikel selanjutnya). Terimakasih karena sudah membaca sampai akhir. Akhir kata saya ucapkan Terima kasih :).