Episode ini membahas cara mendesain tipe yang tetap mudah dirawat saat proyek tumbuh: model domain dengan discriminated union, brand type untuk membedakan nilai sama, komposisi, organisasi file tipe, dan prinsip meminimalkan any dengan memaksimalkan unknown.

Di proyek kecil, tipe apa pun rasanya cukup. Di proyek besar dengan puluhan developer, desain tipe menentukan kecepatan pengembangan. Tipe yang dirancang buruk jadi penghalang; tipe yang dirancang baik menjadi dokumentasi yang menuntun setiap orang menulis kode yang benar.
Scalable berarti tipe tetap ekspresif tanpa menjadi rumit. Kalian tidak ingin union raksasa yang membingungkan, atau tipe yang memaksa konversi terus-menerus karena salah mendesain sumber kebenaran di awal.
Episode 19 membahas pola yang terbukti di codebase besar: memodelkan domain dengan discriminated union, membedakan nilai yang sama bentuknya dengan brand type, komposisi yang sederhana, organisasi file yang konsisten, dan disiplin menghindari any.
Discriminated union dari episode 11 menjadi fondasi memodelkan domain. Keuntungannya muncul saat dipakai konsisten sebagai satu tipe state:
type KeadaanPesan =
| { status: "mengirim"; progress: number }
| { status: "terkirim"; id: string }
| { status: "gagal"; alasan: string };
function deskripsi(k: KeadaanPesan): string {
switch (k.status) {
case "mengirim":
return `Mengirim ${k.progress} persen`;
case "terkirim":
return `Terkirim dengan id ${k.id}`;
case "gagal":
return `Gagal: ${k.alasan}`;
}
}Setiap anggota union membawa data yang relevan untuk state-nya. Tidak ada field opsional yang menumpuk, tidak ada kombinasi tidak valid yang bisa terjadi. Compiler memaksa setiap state ditangani, dan model tumbuh dengan menambah anggota union saja.
Kekuatan pola ini: seluruh aplikasi memakai satu tipe state, lalu tiap modul menurunkan tampilan yang dibutuhkan. Perubahan pada model domain otomatis diperiksa di seluruh pemakaian. Inilah cara menambahkan fitur tanpa merobek banyak file.
TypeScript memakai structural typing: dua tipe dengan bentuk sama dianggap sama. Kadang itu berbahaya:
type IDPengguna = string & { readonly brand: unique symbol };
type IDOrder = string & { readonly brand: unique symbol };
function cariUser(id: IDPengguna): void {}Deklarasi type IDPengguna = string & { readonly brand: unique symbol } menciptakan tipe yang secara struktural berbeda dari string biasa dan dari IDOrder. Fungsi cariUser hanya menerima nilai yang sengaja ditandai sebagai ID pengguna, sehingga tidak mungkin tertukar dengan ID order atau string sembarang.
Kaidahnya: brand type dipakai saat dua nilai berbeda domain memiliki bentuk yang sama, seperti ID dari dua entitas atau satuan yang berbeda. Tanpa ini, kesalahan tukar hanya terdeteksi saat produksi.
Bangun tipe dari potongan kecil yang dikombinasikan:
interface DapatDibuat {
dibuat: Date;
}
interface DapatDiubah {
diubah: Date;
}
type CatatanLengkap = DapatDibuat & DapatDiubah & {
isi: string;
};Tipe dipecah menjadi kesepakatan kecil yang dipakai ulang. Menambahkan properti dibuat ke entitas mana pun cukup dengan intersection. Komposisi menghindari duplikasi dan membuat tipe baru murah untuk dibuat.
Konsistensi lokasi membantu skala:
src/
types/
domain.ts
api.ts
shared.ts
index.tsFolder types menampung definisi domain dan kontrak API secara terpusat, diekspor lewat satu barrel index.ts. Modul lain mengimpor dari satu pintu. Batas yang jelas ini mencegah tipe menyebar di tempat tak terduga dan membuat jejak ketergantungan mudah dipetakan.
Tipe yang scalable dimulai dari disiplin: any adalah titik gagal yang menyebar.
function ambilData(): unknown {
return fetchDataKasar();
}Kembalian unknown memaksa pemanggil melakukan validasi atau narrowing sebelum memakai nilai. Beda dengan any yang langsung membebaskan segalanya. Di episode 22 kalian akan melihat opsi compiler yang menegakkan disiplin ini secara otomatis.
Warning
Kalau menemukan any di codebase, perlakuan yang benar bukan menambah any lain, melainkan mempersempit di satu titik masuk lalu menyebarkan tipe yang sudah pasti. Satu any bisa mencemari tipe lain yang bergantung padanya.
Episode 19 mengajarkan desain tipe yang tumbuh bersama proyek: discriminated union untuk memodelkan domain, brand type untuk membedakan nilai serupa, komposisi dan folder yang terorganisir, serta disiplin meminimalkan any.
Inti yang harus dibawa pulang:
unknown memaksa validasi; any melepas seluruh jaminan.Di episode 20 selanjutnya kita akan membahas build output, bundling, dan source maps, yaitu mengendalikan hasil kompilasi dari tsc sampai artefak produksi.