Episode ini membahas cara mempersempit tipe union menjadi tipe konkret: narrowing dengan typeof, truthiness, equality, in, dan instanceof, plus type predicate dan discriminated union. Kalian juga akan belajar exhaustive check dengan never.

Union type memberi fleksibilitas, tapi memakai anggotanya butuh kehati-hatian. TypeScript tidak membiarkan operasi yang hanya berlaku untuk salah satu anggota union dipanggil tanpa bukti. Proses mempersempit dari tipe luas ke tipe sempit itulah yang disebut type narrowing.
Narrowing bekerja dengan membaca kontrol alur. Setiap pemeriksaan yang konsisten akan dipakai compiler untuk mempersempit tipe pada cabang tertentu. Kemampuan membaca narrowing ini krusial karena union, nullable, dan data dari API ada di mana-mana.
Episode 11 membahas teknik narrowing dari yang paling sederhana sampai pola advanced: operator typeof, truthiness, equality, in, instanceof, type predicate, dan discriminated union. Kalian juga akan menutup celah dengan exhaustive check.
Operator typeof adalah narrow pertama yang dikenali:
function proses(input: string | number): string {
if (typeof input === "string") {
return input.toUpperCase();
}
return input.toFixed(2);
}Di dalam blok if, compiler tahu input pasti string sehingga toUpperCase aman dipanggil. Di luar blok, tipe otomatis menyempit menjadi number. Pemeriksaan typeof input === "string" tidak hanya berfungsi sebagai logika, tapi juga sebagai bukti bagi compiler.
Pemeriksaan langsung menyingkirkan nilai kosong:
function sapa(nama?: string): string {
if (nama) {
return `Halo, ${nama}`;
}
return "Halo, tamu";
}Blok if (nama) menyempit string | undefined menjadi string. Waspada nilai kosong seperti string kosong yang juga falsy. Untuk kasus tertentu, periksa dengan eksplisit !== undefined daripada mengandalkan truthiness.
Perbandingan kesetaraan juga mempersempit:
type Status = "aktif" | "nonaktif" | "pending";
function pesan(status: Status): string {
if (status === "aktif") {
return "Akun berjalan";
}
if (status === "pending") {
return "Menunggu konfirmasi";
}
return "Akun dihentikan";
}Setiap perbandingan literal membuang satu anggota union. Dengan tiga status di atas, cabang terakhir otomatis bertipe "nonaktif". Pola ini paling kuat saat dikombinasikan dengan discriminated union di bawah.
Untuk membedakan bentuk objek, pakai operator in:
interface Pesawat {
sayap: number;
}
interface Mobil {
roda: number;
}
function deskripsi(kendaraan: Pesawat | Mobil): string {
if ("sayap" in kendaraan) {
return `Pesawat dengan ${kendaraan.sayap} sayap`;
}
return `Mobil dengan ${kendaraan.roda} roda`;
}Operator in memeriksa keberadaan properti. Kehadiran sayap membuktikan bentuknya Pesawat. Cara ini efektif saat dua interface tidak berbagi properti yang membedakan.
Narrowing terhadap instance class memakai instanceof:
class ApiError extends Error {}
class JaringanError extends Error {}
function tangani(err: Error): string {
if (err instanceof ApiError) {
return "Gagal memproses request";
}
return "Gangguan jaringan";
}instanceof memeriksa rantai prototype dan mempersempit tipe ke class tertentu. Berguna saat menangani hierarki error atau objek dari library yang berbasis class.
Saat pemeriksaan terlalu kompleks untuk compiler, kalian bisa menulis fungsi guard sendiri:
function adalahObjekPesan(nilai: unknown): nilai adalah { pesan: string } {
return typeof nilai === "object"
&& nilai !== null
&& "pesan" in nilai;
}Signature nilai adalah { pesan: string } memberi tahu compiler bahwa kembalian true berarti argumen memenuhi bentuk tersebut. Type predicate membuat logika pemeriksaan bisa dipakai ulang dan dibaca dengan jelas di banyak tempat.
Pola paling powerful: union objek dengan field pembeda yang sama:
type Aksi =
| { jenis: "simpan"; data: string }
| { jenis: "hapus"; id: number };
function jalankan(a: Aksi): string {
switch (a.jenis) {
case "simpan":
return `Menyimpan: ${a.data}`;
case "hapus":
return `Menghapus id: ${a.id}`;
}
}Field jenis adalah diskriminator. Di setiap case, compiler tahu bentuk objek yang persis, sehingga a.data dan a.id bisa diakses tanpa error. Discriminated union adalah cara paling aman memodelkan state mesin dan event.
Untuk memastikan semua kemungkinan ditangani, tandai default dengan never:
function pastikanTakMungkin(x: never): never {
throw new Error(`Nilai tak tertangani: ${x}`);
}Fungsi pastikanTakMungkin hanya menerima never; di cabang default dari switch di atas, panggil fungsi ini dengan variabel a. Bila kelak ditambahkan anggota union baru, variabel a di default tidak lagi bertipe never, sehingga kompilasi error. Compiler mengingatkan kalian menangani kasus baru, persis pada titik kode yang benar.
Tip
Kombinasi discriminated union dengan switch dan exhaustive check adalah pola paling aman untuk state machine. Perubahan pada model langsung memunculkan error kompilasi di setiap tempat yang menangani state tersebut.
Episode 11 mengubah kalian dari pengguna union menjadi pengendali union. Dengan typeof, truthiness, equality, in, instanceof, type predicate, dan discriminated union, kalian bisa memakai nilai union dengan penuh percaya diri.
Inti yang harus dibawa pulang:
typeof untuk primitif, truthiness untuk nilai kosong.in membedakan bentuk objek.instanceof mempersempit instance class; type predicate memakai is.switch aman per cabang.never memaksa menangani semua kasus.Di episode 12 selanjutnya kita akan membahas advanced mapped types dan conditional types, yaitu alat untuk mentransformasi tipe yang sudah ada menjadi tipe baru secara terprogram.