Narrowing Lanjutan & Discriminated Union
Teknik mempersempit tipe: in, instanceof, type predicate (is), dan pola paling penting di TypeScript — discriminated union dengan switch dan pengecekan kelengkapan.
Mengapa narrowing penting?
Union type (A | B) hanya berguna kalau kita bisa tahu yang mana yang sedang dipegang. Proses menyempitkan union menjadi satu tipe pasti disebut narrowing. Kamu sudah mengenal typeof; bab ini melengkapi perkakasnya.
1. Operator in
Memeriksa apakah sebuah properti ada di object:
type Ikan = { renang: () => void }
type Burung = { terbang: () => void }
function gerak(hewan: Ikan | Burung) {
if ("renang" in hewan) {
hewan.renang() // hewan: Ikan
} else {
hewan.terbang() // hewan: Burung
}
}
2. instanceof
Untuk instance dari class (termasuk Date, Error, Array):
function tampilkan(x: Date | string): string {
if (x instanceof Date) return x.toISOString() // x: Date
return x.toUpperCase() // x: string
}
try { /* ... */ } catch (e) {
if (e instanceof Error) console.log(e.message) // e: Error
}
3. Type predicate: penjaga tipe buatan sendiri
Kalau pemeriksaan kamu rumit, bungkus dalam fungsi dengan kembalian khusus parameter is Tipe:
type Kucing = { nama: string; miaw: () => void }
type Anjing = { nama: string; gonggong: () => void }
function adalahKucing(h: Kucing | Anjing): h is Kucing {
return "miaw" in h
}
function suara(h: Kucing | Anjing) {
if (adalahKucing(h)) h.miaw() // h: Kucing
else h.gonggong() // h: Anjing
}
// Berguna untuk filter: buang null dari array
const data: (string | null)[] = ["a", null, "b"]
const bersih = data.filter((x): x is string => x !== null) // string[]
4. Discriminated union — pola andalan
Beri setiap anggota union satu properti penanda (biasanya bernama jenis, type, atau kind) yang bernilai literal berbeda-beda. TypeScript lalu bisa menentukan anggota mana yang sedang dipakai hanya dari penanda itu.
type Bentuk =
| { jenis: "lingkaran"; radius: number }
| { jenis: "persegi"; sisi: number }
| { jenis: "persegiPanjang"; panjang: number; lebar: number }
function luas(b: Bentuk): number {
switch (b.jenis) {
case "lingkaran":
return Math.PI * b.radius ** 2 // b: {jenis:"lingkaran"; radius}
case "persegi":
return b.sisi ** 2
case "persegiPanjang":
return b.panjang * b.lebar
}
}
Di tiap case, TypeScript tahu persis properti apa yang tersedia. Mengakses b.sisi di case "lingkaran" akan error.
Pengecekan kelengkapan (exhaustiveness)
Bagaimana kalau suatu hari kamu menambah bentuk baru tetapi lupa mengurus luas()? Pakai never sebagai jaring pengaman:
function luas(b: Bentuk): number {
switch (b.jenis) {
case "lingkaran": return Math.PI * b.radius ** 2
case "persegi": return b.sisi ** 2
case "persegiPanjang": return b.panjang * b.lebar
default: {
const tidakMungkin: never = b
throw new Error("Bentuk tak dikenal: " + JSON.stringify(tidakMungkin))
}
}
}
Begitu "segitiga" ditambahkan ke Bentuk, baris const tidakMungkin: never = b berubah merah karena b masih bisa berupa segitiga. Compiler memaksa kamu melengkapi semua kasus. Ini pola yang sangat dipakai untuk status, aksi (Redux), dan hasil API.
Contoh nyata: hasil operasi yang berhasil atau gagal
type Hasil<T> =
| { ok: true; data: T }
| { ok: false; pesan: string }
function bagi(a: number, b: number): Hasil<number> {
if (b === 0) return { ok: false, pesan: "Tidak bisa dibagi nol" }
return { ok: true, data: a / b }
}
const h = bagi(10, 2)
if (h.ok) console.log(h.data) // data tersedia
else console.log(h.pesan) // pesan tersedia
(Hasil<T> memakai generics — dibahas di bab 13. Untuk sekarang, baca sebagai "Hasil yang datanya bertipe T".)
| Teknik | Cocok untuk |
|---|---|
typeof | Primitif (string, number, boolean) |
in | Object dengan properti berbeda |
instanceof | Instance class / Error / Date |
| Type predicate | Pemeriksaan rumit yang dipakai berulang |
| Discriminated union | Beberapa varian data — pilihan utama |
Kesalahan umum pemula
- Memakai properti penanda bertipe
stringbiasa, bukan literal — narrowing tidak bekerja. - Lupa
return/breakpadaswitch. - Menulis type predicate yang berbohong (mengembalikan
truepadahal bukan tipenya). TypeScript percaya saja pada klaimmu.