September 28, 2026

Generics & Utility Types.

Membuat tipe yang reusable di TypeScript: generics dan constraint, keyof & typeof, indexed access, mapped types, conditional types, serta utility types bawaan seperti Partial, Pick, Omit, Record, dan ReturnType.

Setelah paham dasar TypeScript , langkah berikutnya adalah membuat tipe yang tidak diketik ulang. Generics membuat fungsi dan komponen bekerja untuk banyak tipe sekaligus, sedangkan utility types dan mapped types memungkinkan kita menurunkan tipe baru dari tipe yang sudah ada β€” satu sumber kebenaran, banyak turunan.


1. Generics

Tanpa generics, kita harus memilih antara tipe spesifik (tidak reusable) atau any (tidak aman):

function pertamaAny(arr: any[]): any {
  return arr[0];
}
const x = pertamaAny([1, 2, 3]); // x: any πŸ˜•

Dengan generics, tipe menjadi parameter:

function pertama<T>(arr: T[]): T | undefined {
  return arr[0];
}

const a = pertama([1, 2, 3]);        // a: number | undefined
const b = pertama(["x", "y"]);       // b: string | undefined
const c = pertama<boolean>([true]);  // eksplisit (jarang perlu)

T diinfer otomatis dari argumen. Namanya bebas; konvensinya T, K (key), V (value), E (element).

Generic pada type & interface

interface ApiResponse<T> {
  data: T;
  meta: { page: number; total: number };
}

type User = { id: number; nama: string };

async function getUsers(): Promise<ApiResponse<User[]>> {
  const res = await fetch("/api/users");
  return res.json();
}

Pola ini sangat umum saat mengambil data dari API : satu tipe wrapper untuk semua endpoint.

Constraint dengan extends

Batasi T agar punya properti tertentu:

function panjang<T extends { length: number }>(x: T): number {
  return x.length;
}

panjang("halo");      // βœ…
panjang([1, 2, 3]);   // βœ…
// panjang(42);       // ❌ number tidak punya length

Default type parameter

type Paginated<T = unknown> = { items: T[]; next: string | null };

const p: Paginated = { items: [], next: null }; // T = unknown

2. keyof, typeof, dan Indexed Access

Tiga operator ini adalah “bahan baku” untuk menurunkan tipe.

type Produk = { id: number; nama: string; harga: number };

type KunciProduk = keyof Produk;      // "id" | "nama" | "harga"
type Harga = Produk["harga"];         // number (indexed access)
type NilaiProduk = Produk[keyof Produk]; // number | string

typeof di posisi tipe mengambil tipe dari nilai:

const config = {
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retry: true,
};

type Config = typeof config;
// { apiUrl: string; timeout: number; retry: boolean }

Kombinasi klasik β€” fungsi getter yang aman:

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

const p: Produk = { id: 1, nama: "Jam", harga: 250000 };
const h = ambil(p, "harga"); // h: number
// ambil(p, "stok");         // ❌ "stok" bukan key Produk

as const untuk turunan dari array

const ROLES = ["admin", "editor", "viewer"] as const;
type Role = (typeof ROLES)[number]; // "admin" | "editor" | "viewer"

function isRole(x: string): x is Role {
  return (ROLES as readonly string[]).includes(x);
}

Array runtime dan tipe union kini selalu sinkron β€” tambah role cukup di satu tempat.


3. Utility Types Bawaan

TypeScript menyediakan utility types global. Yang paling sering dipakai:

UtilityHasilContoh kasus
Partial<T>Semua properti opsionalPayload update (PATCH)
Required<T>Semua properti wajibKonfigurasi setelah diisi default
Readonly<T>Semua properti read-onlyState immutable
Pick<T, K>Ambil sebagian keyDTO publik
Omit<T, K>Buang sebagian keyForm create (tanpa id)
Record<K, V>Objek dengan key K bernilai VLookup table / map
Exclude<U, M>Buang anggota unionStatus tanpa "archived"
Extract<U, M>Ambil anggota union yang cocokFilter union
NonNullable<T>Buang null & undefinedSetelah validasi
ReturnType<F>Tipe return fungsiTipe dari fungsi pihak ketiga
Parameters<F>Tuple parameter fungsiWrapper/decorator
Awaited<T>Isi dari PromiseHasil async function

Contoh nyata dari satu model:

interface Artikel {
  id: number;
  judul: string;
  isi: string;
  status: "draft" | "published" | "archived";
  createdAt: Date;
}

type ArtikelBaru = Omit<Artikel, "id" | "createdAt">;
type ArtikelUpdate = Partial<ArtikelBaru>;
type ArtikelRingkas = Pick<Artikel, "id" | "judul">;
type StatusAktif = Exclude<Artikel["status"], "archived">; // "draft" | "published"

const labelStatus: Record<Artikel["status"], string> = {
  draft: "Draf",
  published: "Terbit",
  archived: "Arsip",
};

Jika nanti status bertambah "scheduled", labelStatus langsung error karena key-nya belum lengkap β€” Record dengan key union memaksa kelengkapan.

ReturnType dan Awaited berguna saat tipe hanya tersedia lewat fungsi:

async function fetchProfil() {
  return { id: 1, nama: "Fanny", skills: ["PHP", "TS"] };
}

type Profil = Awaited<ReturnType<typeof fetchProfil>>;
// { id: number; nama: string; skills: string[] }

4. Mapped Types

Utility types di atas sebenarnya dibangun dengan mapped types: iterasi atas key sebuah tipe.

type OpsionalSemua<T> = {
  [K in keyof T]?: T[K];
};

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

Modifier readonly dan ? bisa ditambah (+) atau dihapus (-):

type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

Key remapping dengan as

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type User = { nama: string; umur: number };
type UserGetters = Getters<User>;
// { getNama: () => string; getUmur: () => number }

Template literal type (`get${...}`) dan intrinsic Capitalize, Uppercase, Lowercase, Uncapitalize membuat transformasi nama key jadi mudah.


5. Conditional Types (Sekilas)

Bentuknya T extends U ? X : Y β€” seperti ternary untuk tipe:

type IsString<T> = T extends string ? true : false;

type A = IsString<"halo">; // true
type B = IsString<42>;     // false

// infer: "ekstrak" bagian dari tipe
type ElemenArray<T> = T extends (infer E)[] ? E : never;
type C = ElemenArray<number[]>; // number

Conditional type bersifat distributive terhadap union: IsString<string | number> menjadi boolean (true | false). Untuk kebanyakan kode aplikasi, cukup tahu bahwa ini yang membuat Exclude, Extract, dan ReturnType bekerja β€” jarang perlu menulis sendiri.


Kesalahan Umum

  1. Generic yang tidak perlu. function f<T>(x: T): void tanpa memakai T di tempat lain hanya menambah noise. Generic berguna jika menghubungkan input dengan output.
  2. Constraint terlupa. Mengakses x.length pada T tanpa extends { length: number } akan error.
  3. Menduplikasi tipe secara manual. Menulis ulang ArtikelBaru dengan tangan membuatnya cepat basi; turunkan dengan Omit/Pick.
  4. Omit dengan key salah ketik. Omit<T, "judull"> tidak error (key-nya bertipe string bebas). Periksa hasilnya dengan hover di editor.
  5. Lupa as const. Tanpa itu, ["admin", "editor"] bertipe string[] dan union turunan menjadi string.
  6. Conditional type yang terlalu “pintar”. Tipe yang sulit dibaca sama buruknya dengan kode yang sulit dibaca β€” utamakan kejelasan.
  7. Partial untuk semua hal. Membuat semua properti opsional di mana-mana justru menghilangkan jaminan; pakai hanya di batas yang memang parsial (update, draft form).

Ringkasan

  • Generics = parameter tipe; berguna saat menghubungkan tipe input dan output.
  • keyof, typeof, dan T[K] adalah alat untuk menurunkan tipe dari yang sudah ada.
  • Utility types (Partial, Pick, Omit, Record, ReturnType, Awaited) menutup 90% kebutuhan sehari-hari.
  • Mapped & conditional types adalah fondasi di balik utility types.
  • Lanjut ke TypeScript di Proyek Nyata untuk konfigurasi tsconfig dan integrasi CI.

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.

Comments