Langsung ke konten
Rangga Prastio
EN
← Kembali

Pola TypeScript Yang Saya Pakai Setiap Hari

Pola TypeScript praktis dari pekerjaan production sehari-hari yang bikin kode lebih rapi, bug lebih sedikit, dan codebase lebih gampang dirawat.

Terbit , typescript, pola, best-practices, frontend, react

Nilai TypeScript bukan sekadar menangkap typo. Digunakan dengan baik, TypeScript mengkodekan aturan bisnis, mendokumentasikan maksud, dan menghilangkan seluruh kategori bug sebelum mencapai production. Ini pola-pola yang paling sering saya pakai dalam pekerjaan profesional.

Discriminated Unions untuk State

Hindari masalah “impossible state” dengan membuat state ilegal tidak bisa direpresentasikan.

type LoadState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: string };

Sekarang kamu tidak mungkin tidak sengaja membaca data saat status-nya error, karena TypeScript akan langsung menangkapnya. Bandingkan dengan pola umum berupa boolean { isLoading, isError, data, error }, yang memungkinkan isLoading: true dan isError: true muncul bersamaan. Kondisi itu tidak ada artinya.

Branded Types untuk Domain Primitives

Ketika dua nilai memiliki tipe dasar yang sama tapi semantik berbeda, beri mereka brand.

type UserId = string & { readonly _brand: 'UserId' };
type ProductId = string & { readonly _brand: 'ProductId' };

function createUserId(id: string): UserId {
  return id as UserId;
}

Sekarang kamu tidak bisa secara tidak sengaja melewatkan ProductId di mana UserId yang diharapkan, meskipun keduanya adalah string saat runtime. Ini menangkap kelas bug yang mengejutkan umumnya di codebase yang menangani banyak tipe entitas.

satisfies untuk Config Objects

Operator satisfies memvalidasi nilai terhadap tipe tanpa melebarkannya.

const routes = {
  home: '/',
  about: '/about',
  portfolio: '/portfolio',
} satisfies Record<string, string>;

// routes.home bertipe '/' (literal), bukan string

Tanpa satisfies, kamu harus memilih antara type safety (yang melebarkan ke string) atau inferensi (yang kehilangan pengecekan tipe). satisfies memberi kamu keduanya.

as const + Inferensi Tipe dari Nilai

Definisikan datamu sekali dan turunkan tipe dari sana.

const STATUSES = ['pending', 'active', 'cancelled'] as const;
type Status = typeof STATUSES[number]; // 'pending' | 'active' | 'cancelled'

Pola ini memastikan tipe dan array runtime-mu tetap sinkron secara otomatis. Jika kamu menambahkan status baru ke array, tipenya ikut terupdate. Jika kamu salah tulis di tipe, compiler akan menangkapnya.

Exhaustive Checks di Switch Statement

function assertNever(value: never): never {
  throw new Error(`Unhandled case: ${JSON.stringify(value)}`);
}

function handleStatus(status: Status): string {
  switch (status) {
    case 'pending': return 'Menunggu...';
    case 'active': return 'Berjalan';
    case 'cancelled': return 'Dihentikan';
    default: return assertNever(status);
  }
}

Kalau nanti kamu menambah nilai Status baru dan lupa memperbarui switch ini, compiler TypeScript akan memberi error di pemanggilan assertNever. Inilah exhaustive checking: compiler membuktikan semua kemungkinan sudah ditangani.

Utility Types dalam Praktik

Beberapa yang sering saya gunakan:

// Ambil hanya yang dibutuhkan
type UserPreview = Pick<User, 'id' | 'name' | 'avatarUrl'>;

// Buat semua field opsional untuk partial update
type UserUpdate = Partial<Pick<User, 'name' | 'email'>>;

// Readonly untuk data immutable
type Config = Readonly<AppConfig>;

// Record untuk dictionary
type StatusColors = Record<Status, string>;

Partial<Pick<T, K>> sangat berguna untuk payload update. Kamu tetap mendapat type safety untuk field yang boleh diubah, tanpa harus membuat semuanya wajib diisi.

Generic Components di React

Beri tipe pada props komponen secara generik ketika menerima bentuk data yang bervariasi.

interface ListProps<T> {
  items: T[];
  getKey: (item: T) => string;
  renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, getKey, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={getKey(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

Hasilnya komponen list generik yang sepenuhnya type-safe. TypeScript menebak T dari prop items, jadi pemakainya tidak perlu menuliskannya sendiri.

Prinsip di Balik Semua Ini

Semua pola di atas punya satu tujuan: memindahkan potensi error dari runtime ke compile-time. Error di runtime sampai ke pengguna. Error di compile-time cukup sampai ke kamu. Inti TypeScript bukan soal anotasi tipe, tapi soal mempersempit jarak antara menulis kode dan yakin kodenya benar.

Ada bagian yang bikin tim mentok? Kirim ke saya.

Terbuka untuk freelance dan kontrak, 20–40 jam per minggu. Biasanya saya balas dalam 24 jam.

Kirim email

ranggaprastio123@gmail.com

Jam kerja kita

Jam kamu
--:--
Jam Rangga
19:58
UTC+7