Daftar Isi

Mengenal TypeScript: Generics

This article has been translated into English version: Learn TypeScript: Generics

Sebelumnya kita sudah pernah membahas secara sekilas tentang penggunaan Generics di Mengenal TypeScript: Interface & Type dan Mengenal TypeScript: Type Assertions, Type Narrowing, & Function, tapi kali ini kita akan membahas lebih detil tentang apa itu Generics di TypeScript. TypeScript Generics adalah fitur yang memungkinkan kita membuat komponen yang dapat bekerja dengan berbagai tipe data, bukan hanya satu tipe. Artikel ini akan menjelaskan konsep, sintaks, dan penggunaan praktis dari Generics di TypeScript.

Apa itu Generics?

Generics memungkinkan kita membuat fungsi, kelas, atau interface yang dapat bekerja dengan berbagai tipe data sambil tetap mempertahankan keamanan tipe. Dengan Generics, kita bisa menulis kode yang lebih fleksibel, dapat digunakan kembali, dan tetap type-safe.

Mari kita mulai dengan contoh fungsi generics sederhana:

/** Fungsi generics yang mengembalikan argumen yang sama */
function identity<T>(arg: T): T {
  return arg;
}

/** Menggunakan fungsi dengan tipe eksplisit */
const output1 = identity<string>("Hello");  // output1 bertipe string
const output2 = identity<number>(42);       // output2 bertipe number

/** TypeScript juga bisa menyimpulkan tipe secara otomatis */
const output3 = identity("World");          // output3 bertipe string
const output4 = identity(100);              // output4 bertipe number

Pada contoh di atas, T adalah parameter tipe yang akan digantikan dengan tipe aktual saat fungsi dipanggil. Fungsi identity akan menerima argumen bertipe T dan mengembalikan nilai bertipe T juga.

Multiple Type Parameters

Generics juga bisa menggunakan lebih dari satu parameter tipe:

/** Fungsi dengan dua parameter tipe */
function pair<T, U>(first: T, second: U): [T, U] {
  return [first, second];
}

const result1 = pair<string, number>("key", 42);      // [string, number]
const result2 = pair<boolean, string[]>(true, ["a"]); // [boolean, string[]]

/** Dengan type inference */
const result3 = pair("hello", 100);                   // [string, number]

Generic Constraints

Terkadang kita ingin membatasi tipe yang bisa digunakan dengan generics. Ini bisa dilakukan dengan generic constraints:

/** Interface untuk constraint */
interface HasLength {
  length: number;
}

/** Fungsi yang hanya menerima tipe dengan property length */
function logLength<T extends HasLength>(arg: T): T {
  console.log(`Length: ${arg.length}`);
  return arg;
}

/** Valid karena string memiliki property length */
logLength("Hello");        // Length: 5

/** Valid karena array memiliki property length */
logLength([1, 2, 3]);      // Length: 3

/** Valid karena objek memiliki property length */
logLength({ length: 10 }); // Length: 10

/** Error: number tidak memiliki property length */
// logLength(42);

Dengan constraint T extends HasLength, kita membatasi tipe T hanya untuk tipe yang memiliki property length bertipe number.

Generic Interface dan Type

Generics juga bisa digunakan dengan interface dan type alias:

/** Generic interface */
interface Box<T> {
  value: T;
}

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

/** Generic type alias */
type Pair<T, U> = {
  first: T;
  second: U;
};

const keyValue: Pair<string, number> = { first: "key", second: 42 };

Generic Classes

Kita juga bisa membuat kelas generik:

/** Generic class */
class Container<T> {
  private item: T;

  constructor(item: T) {
    this.item = item;
  }

  getItem(): T {
    return this.item;
  }

  setItem(item: T): void {
    this.item = item;
  }
}

const numberContainer = new Container<number>(123);
console.log(numberContainer.getItem()); // 123

const stringContainer = new Container<string>("Hello");
console.log(stringContainer.getItem()); // "Hello"

Default Type Parameters

Generics juga bisa memiliki nilai default:

/** Interface dengan parameter tipe default */
interface ApiResponse<T = any> {
  data: T;
  status: number;
  message: string;
}

/** Tidak perlu menentukan tipe */
const response1: ApiResponse = {
  data: { name: "John" },
  status: 200,
  message: "Success",
};

/** Menentukan tipe secara eksplisit */
interface User {
  id: number;
  name: string;
}

const response2: ApiResponse<User> = {
  data: { id: 1, name: "John" },
  status: 200,
  message: "Success",
};

Generic Type Inference

TypeScript bisa menyimpulkan tipe generik dalam banyak kasus:

/** Fungsi generik dengan inference */
function firstElement<T>(arr: T[]): T | undefined {
  return arr[0];
}

/** TypeScript menyimpulkan T sebagai string */
const first = firstElement(["a", "b", "c"]); // first: string | undefined

/** TypeScript menyimpulkan T sebagai number */
const second = firstElement([1, 2, 3]);      // second: number | undefined

/** TypeScript menyimpulkan T sebagai union type */
const mixed = firstElement([1, "a", true]);  // mixed: string | number | boolean | undefined

Contoh Penggunaan Generics

Berikut ini beberapa contoh penggunaan generics yang sering ditemui saat kita membuat aplikasi menggunakan TypeScript.

/** Fungsi map generik */
function map<T, U>(array: T[], fn: (item: T) => U): U[] {
  return array.map(fn);
}

const numbers = [1, 2, 3, 4];
const doubled = map(numbers, n => n * 2);            // [2, 4, 6, 8]
const strings = map(numbers, n => n.toString());     // ["1", "2", "3", "4"]
const booleans = map(numbers, n => n % 2 === 0);     // [false, true, false, true]
/** Generic component wrapper */
interface Props<T> {
  data: T;
  render: (item: T) => string;
}

function Wrapper<T>(props: Props<T>): string {
  return props.render(props.data);
}

const numberWrapper = Wrapper({
  data: 42,
  render: (num) => `Number: ${num}`,
});

const userWrapper = Wrapper({
  data: { name: "John", age: 30 },
  render: (user) => `User: ${user.name}, Age: ${user.age}`,
});

console.log(numberWrapper); // "Number: 42"
console.log(userWrapper);   // "User: John, Age: 30"
/** Generic API function */
async function fetchData<T>(url: string): Promise<T> {
  const response = await fetch(url);
  return response.json() as Promise<T>;
}

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

interface Product {
  id: number;
  title: string;
  price: number;
}

/** Type-safe API calls */
async function loadData() {
  const user = await fetchData<User>("https://api.example.com/user/1");
  console.log(user.name); // TypeScript knows user has name property

  const product = await fetchData<Product>("https://api.example.com/product/1");
  console.log(product.price); // TypeScript knows product has price property
}

Keypoint Generics

Berikut ini beberapa keypoint dari Generics:

  1. Reusability

    Generics memungkinkan kita menulis kode yang dapat digunakan kembali dengan berbagai tipe data.

  2. Type Safety

    Meskipun fleksibel, generics tetap mempertahankan keamanan tipe.

  3. Better Inference

    TypeScript dapat menyimpulkan tipe generik dalam banyak kasus.

  4. Constraints

    Kita dapat membatasi tipe yang bisa digunakan dengan generics.

  5. Readability

    Generics membuat kode lebih ekspresif dan self-documenting.

Kesimpulan

Generics adalah fitur yang sangat kuat di TypeScript yang memungkinkan kita membuat komponen yang fleksibel namun tetap type-safe. Dengan memahami dan memanfaatkan generics, kita dapat menulis kode yang lebih reusable, ekspresif, dan aman. Pendekatan functional programming menjadi lebih kuat ketika dikombinasikan dengan generics, memungkinkan kita membuat fungsi-fungsi utilitas yang dapat bekerja dengan berbagai tipe data tanpa mengorbankan keamanan tipe.

Konten Terkait