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.