# Bagaimana Menggunakan Generics pada TypeScript

Selamat datang pada pembahasan Typescript! Hari ini, kita akan mempelajari `generik`, sebuah feature di Typescript yang powerful yang memungkinkan kita untuk membuat kemponen yang dapat digunakan kembali. Generik memberikan sebuah cara untuk membuat komponen-komponen yang berkerja dengan banyak tipe data dan memberikan kemanan tipe pada saat yang bersamaan.

### 1\. Perkenalan Generik

Generik memungkinkan kita untuk menulis fungsi, kelas dan *interface* yang fleksibel dan dapat digunakan berulang-ulang. Generik memungkinkan kita membuat komponen-komponen yang dapat bekerja dengan banyak tipe data tanpa kehilangan informasi tipenya.

**Fungsi Generik Dasar**

```javascript
function identity<T>(arg: T): T {
  return arg;
}

let output1 = identity<string>("myString"); // Output: "myString"
let output2 = identity<number>(42); // Output: 42
```

Pada contoh ini, `T` adalah tipe parameter yang bertindak sebagai sebuah *placeholder* untuk tipe yang asli. Ketika fungsinya dipanggil, tipe parameternya telah diganti dengan tipe yang diberikan.

### 2\. Fungsi Generik

Kita dapat membuat fungsi generik yang bekerja dengan banyak tipe data. Ini berguna khususnya untuk fungsi yang bekerja dengan *array* atau koleksi.

**Contoh: Fungsi Generik**

```javascript
function loggingIdentity<T>(arg: T[]): T[] {
  console.log(arg.length); // Array has a `.length` property
  return arg;
}

let stringArray = loggingIdentity<string>(["Alice", "Bob", "Charlie"]);
```

Disini, fungsi `loggingIdentity` bekerja dengan sebuah *array* dari semua tipe `T`. Tipe *array* ditentukan ketika fungsinya dipanggil.

### 3\. Kelas Generik

Generik dapat digunakan untuk membuat kelas yang bekerja dengan berbagai tipe. Ini berguna untuk struktur data seperti *stacks*, *queues* atau *linked lists*.

**Contoh: Kelas Generik**

```javascript
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;

  constructor(zeroValue: T, addFunction: (x: T, y: T) => T) {
    this.zeroValue = zeroValue;
    this.add = addFunction;
  }
}

let myNumber = new GenericNumber<number>(0, (x, y) => x + y);
console.log(myNumber.add(5, 10)); // Output: 15

let myString = new GenericNumber<string>("", (x, y) => x + y);
console.log(myString.add("Hello, ", "world!")); // Output: "Hello, world!"
```

Pada contoh ini, kelas `GenericNumber` bekerja dengan berbagai tipe `T`. Tipenya dispesifikasikan ketika sebuah *instance* dari kelas dibuat.

### 4\. *Constraints* Generik

Terkadang, kita perlu untuk membatasi tipe-tipe yang dapat digunakan dengan generik. Kita dapat mencapainya dengan constraints.

**Contoh: *Constraints* Generik**

```javascript
interface Lengthwise {
  length: number;
}

function loggingIdentityWithConstraint<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); // Now we know it has a `.length` property
  return arg;
}

loggingIdentityWithConstraint({ length: 10, value: 42 }); // Works
// loggingIdentityWithConstraint(42); // Error: Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
```

Disini, tipe *generic* `T` telah dibatasi untuk tipe-tipe yang memiliki properti `length`.

### 5\. Menggunakan Beberapa Parameter Tipe

Generik dapat juga menggunakan beberapa parameter tipe untuk membuat tipe-tipe yang lebih fleksibel dan kompleks.

**Contoh: Beberapa Parameter Tipe**

```javascript
function merge<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

let mergedObject = merge({ name: "Alice" }, { age: 25 });
console.log(mergedObject); // Output: { name: "Alice", age: 25 }
```

Pada contoh ini, fungsi `merge` menerima 2 tipe objek yang berbeda dan menggabungkannya menjadi satu.

### 6\. *Interfaces* Generik

*Interfaces* dapat juga menggunakan generics untuk membuat kontrak untuk fungsi, kelas dan objek yang fleksibel dan dapat digunakan kembali.

**Contoh: *Interfaces* Generik**

```javascript
interface KeyValuePair<K, V> {
  key: K;
  value: V;
}

let kvp: KeyValuePair<string, number> = { key: "age", value: 30 };
console.log(kvp); // Output: { key: "age", value: 30 }
```

Disini, *interface* `keyValuePair` menerima dua parameter, `K` dan `V`, membuat sebuah generik *interface*.

### 7\. Parameter Tipe Bawaan

Kita dapat memberikan tipe bawaan untuk generik kit, membuat opsi generik ketika memanggil fungsi atau membuat sebuah *instance* dari sebuah kelas.

**Contoh: Parameter Tipe Bawaan**

```javascript
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

let stringArray = createArray(3, "hello");
let numberArray = createArray<number>(3, 42);
console.log(stringArray); // Output: ["hello", "hello", "hello"]
console.log(numberArray); // Output: [42, 42, 42]
```

Pada contoh ini, jika tipe tidak diberikan, maka type `string` bawaan akan digunakan.

### 8\. Constraints dan keyof Generik

Menggunakan `typeof` dengan generik memungkinkan kita untuk membuat fungsi yang beroperasi pada properti-properti objek yang spesifik.

**Contoh: *Constraints* dan** `keyof` **Generik**

```javascript
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let person = { name: "Alice", age: 25 };
let name = getProperty(person, "name"); // Output: "Alice"
let age = getProperty(person, "age"); // Output: 25
// let unknown = getProperty(person, "unknown"); // Error: Argument of type '"unknown"' is not assignable to parameter of type 'keyof { name: string; age: number; }'.
```

Disini, fungsi `getProperty` menerima sebuah object `T` dan key `K` yang harus menjadi sebuah `key` dari `T`. Ini memastikan keamanan tipe.

### Contoh Kode

Disini sebuah contoh yang mencontohkan penggunaan dari generik pada fungsi, kelas dan *interface*:

```javascript
// Generic function
function reverseArray<T>(items: T[]): T[] {
  return items.reverse();
}

let reversedNumbers = reverseArray([1, 2, 3, 4, 5]);
console.log(reversedNumbers); // Output: [5, 4, 3, 2, 1]

// Generic class
class DataStorage<T> {
  private data: T[] = [];

  addItem(item: T) {
    this.data.push(item);
  }

  removeItem(item: T) {
    this.data = this.data.filter(i => i !== item);
  }

  getItems(): T[] {
    return [...this.data];
  }
}

let textStorage = new DataStorage<string>();
textStorage.addItem("Alice");
textStorage.addItem("Bob");
textStorage.removeItem("Alice");
console.log(textStorage.getItems()); // Output: ["Bob"]

// Generic interface
interface Pair<T, U> {
  first: T;
  second: U;
}

let pair: Pair<string, number> = { first: "Alice", second: 25 };
console.log(pair); // Output: { first: "Alice", second: 25 }
```

### Rangkuman

Hari ini, kita telah mempelajari generics pada Typescript, membahas fungsi, kelas, *constraint*, multiple parameter tipe, *interface*, parameter tipe bawaan, dan operator `keyof` generik.  
Generik adalah sebuah fitur yang powerfull yang memungkinkan kita membuat komponen yang fleksibel, dapat digunakan kembali dan sebagai keamanan tipe.

### Sumber

* [https://anuj1.hashnode.dev/generics-in-typescript](https://anuj1.hashnode.dev/generics-in-typescript)
