Narrowing dengan typeof
Persempit union jadi tipe spesifik dengan typeof: teknik narrowing paling dasar.
Kenapa narrowing itu ada
Bayangin kamu nulis fungsi yang menerima string | number. Union itu janji ke compiler: "nilainya salah satu dari dua ini". Masalahnya, compiler nggak bisa nebak yang mana yang datang saat runtime. Kalau kamu langsung panggil method khusus string, dia protes duluan:
function cetak(x: string | number) {
console.log(x.toUpperCase()); // ERROR di sini
}Pesan errornya:
error TS2339: Property 'toUpperCase' does not exist on type 'string | number'.Itu bukan compiler yang rese. Dia jagain kamu: kalau x ternyata number, .toUpperCase() bakal crash saat runtime. Solusinya bukan casting, tapi narrowing: buktikan dulu tipenya lewat pengecekan runtime, baru pakai method spesifiknya. typeof adalah cara paling dasar buat ngebuktiin itu.
Alur narrowing langkah demi langkah
Skenario nyata: kamu baca parameter dari URL. Awalnya string, tapi setelah parsing bisa jadi number, atau null kalau param-nya nggak ada.
type NilaiParam = string | number | null;
function normalkan(p: NilaiParam): string {
// Langkah 1: singkirkan null dengan guard clause.
// Setelah baris ini, p: string | number
if (p === null) {
return "default";
}
// Langkah 2: cek typeof untuk string.
// Di dalam if, p: string. Di bawahnya, p: number.
if (typeof p === "string") {
return p.trim().toUpperCase(); // aman, p pasti string
}
// Langkah 3: yang tersisa cuma number.
return `N:${p.toFixed(0)}`; // aman, p pasti number
}Pola pikirnya: narrowing itu eliminasi bertahap. Setiap pengecekan menyingkirkan satu (atau beberapa) anggota union. Compiler melacak tipe yang "masih mungkin" di setiap baris kode. Di akhir fungsi, cuma satu tipe yang tersisa, jadi method spesifiknya boleh dipakai tanpa protes.
Error compiler yang paling sering ketemu
Lupa narrowing sebelum memanggil method spesifik:
declare function ambilInput(): string | number;
const v = ambilInput();
const hasil = v.toFixed(2);error TS2339: Property 'toFixed' does not exist on type 'string | number'.
Property 'toFixed' does not exist on type 'string'.Compiler bahkan nunjuk cabang mana yang bermasalah (string nggak punya toFixed). Fix-nya satu baris: bungkus dengan if (typeof v === "number").
Kapan dipakai di project nyata
- Parsing input form, query URL, atau env var: semua datang sebagai string, tapi logika butuh number atau boolean. Narrow dulu, konversi kemudian.
- Fungsi util yang fleksibel: misal
format(nilai: string | number | Date)yang cara formatnya beda per tipe. - Data JSON dari API: field bertipe
string | nullharus di-narrow sebelum dirender, biar nggak kenaCannot read properties of nulldi browser.
Jebakan typeof yang wajib dihafal
typeof null hasilnya "object". Ini bug legendaris JavaScript yang nggak akan pernah diperbaiki. Jadi jangan pakai typeof buat null:
function cek(x: object | null) {
if (typeof x === "object") {
// x MASIH bisa null di sini. Jebakan!
}
if (x !== null) {
// x: object. Baru aman.
}
}Untuk null dan undefined, pakai perbandingan langsung (=== null, === undefined) atau optional chaining.
Catatan teknis: Truthiness check (
if (x)) juga mempersempit tipe, tapi dia menyingkirkan SEMUA nilai falsy:"",0,false,NaN. Kalau0atau string kosong adalah nilai yang valid di domain kamu, pakai perbandingan eksplisit (x !== undefined), jangan truthiness.
Tantangan
Normalisasi input
Buat fungsi normalisasi(x: string | number | undefined): string yang mengembalikan: string uppercase bila string, string angka dengan awalan "N:" bila number, dan "KOSONG" bila undefined. Pakai narrowing typeof dan guard clause.
function normalisasi(x: string | number | undefined): string {
// guard clause untuk undefined, lalu narrowing
}
console.log(normalisasi("halo")); // "HALO"
console.log(normalisasi(42)); // "N:42"
console.log(normalisasi(undefined)); // "KOSONG"