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.
Fungsi Generics Sederhana
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 Utilitas Generik
/** 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]
Wrapper Komponen (React-style)
/** 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"
Type-safe API Calls
/** 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:
-
Reusability
Generics memungkinkan kita menulis kode yang dapat digunakan kembali dengan berbagai tipe data.
-
Type Safety
Meskipun fleksibel, generics tetap mempertahankan keamanan tipe.
-
Better Inference
TypeScript dapat menyimpulkan tipe generik dalam banyak kasus.
-
Constraints
Kita dapat membatasi tipe yang bisa digunakan dengan generics.
-
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.