Type narrowing adalah cara TypeScript mempersempit type sebuah variabel di dalam satu cabang kode, berdasarkan pengecekan yang kamu tulis. Mirip menerima paket yang isinya "buku atau baju". Sebelum dibuka, kamu gak bisa langsung menaruhnya di rak buku. Begitu dibuka dan ternyata buku, kamu tahu pasti isinya buku. Kalau ternyata bukan buku, sisanya pasti baju.
🧪 Tebakan cepat:
typeof ["a", "b"]hasilnya apa:"array","object", atau"string"? Tebak dulu, jawabannya ada di bagian "Array: pakai Array.isArray".
typeof: cek type dasar
Di pelajaran union, kamu sudah memakai typeof sekali. Sekarang kita lihat apa yang TypeScript lakukan di tiap baris:
function panjang(input: string | string[]): number {
if (typeof input === "string") {
return input.length; // input: string
}
return input.length; // input: string[]
}
console.log(panjang("hello")); // 5
console.log(panjang(["a", "b", "c"])); // 3
Kalau kursor diarahkan ke input di editor, type yang muncul berbeda di tiap tempat:
| Di mana | Type input |
Kenapa |
|---|---|---|
| awal fungsi | string | string[] |
belum ada pengecekan |
di dalam if |
string |
typeof input === "string" bernilai true |
setelah if |
string[] |
cabang string sudah return, jadi yang sampai ke sini pasti array |
typeofadalah operator JavaScript yang mengembalikan nama jenis nilai sebagai teks:"string","number","boolean","undefined","object","function".- TypeScript mengikuti
ifdanreturn-mu. Kamu gak menulis type baru; dia menyimpulkannya sendiri dari pengecekan. .lengthada di dua cabang, tapi artinya beda: banyaknya karakter ("hello"= 5) dan banyaknya isi array (["a", "b", "c"]= 3).
Array: pakai Array.isArray
typeof ["a", "b"] hasilnya "object", bukan "array". Itu jawaban tebakannya. Di JavaScript, array termasuk object, jadi typeof gak bisa membedakan array dari object lain. Untuk array, pakai Array.isArray:
function panjang(input: string | string[]): number {
if (Array.isArray(input)) {
return input.length; // input: string[]
}
return input.length; // input: string
}
TypeScript juga paham Array.isArray: di dalam if, input jadi string[], dan setelahnya tinggal string.
in dan instanceof: cek object
Kalau anggota union-nya object, pakai in atau instanceof.
"nama" in objek mengecek apakah objek punya properti dengan nama itu:
type Ikan = { berenang: () => string };
type Kucing = { meong: () => string };
function gerak(hewan: Ikan | Kucing) {
if ("berenang" in hewan) {
return hewan.berenang(); // hewan: Ikan
}
return hewan.meong(); // hewan: Kucing
}
nilai instanceof Kelas mengecek apakah nilai itu dibuat dari sebuah class, misalnya Date:
function tahun(tanggal: string | Date) {
if (tanggal instanceof Date) {
return tanggal.getFullYear(); // tanggal: Date
}
return tanggal; // tanggal: string
}
Discriminated union: cek satu properti pembeda
Cara yang paling sering dipakai di aplikasi sungguhan: tiap anggota union punya properti yang sama, jenis, dengan isi literal yang beda. Properti ini namanya pembeda (discriminant).
type Lingkaran = { jenis: "lingkaran"; jari: number };
type Persegi = { jenis: "persegi"; sisi: number };
type Bentuk = Lingkaran | Persegi;
function luas(b: Bentuk): number {
if (b.jenis === "lingkaran") {
return Math.PI * b.jari * b.jari; // b: Lingkaran
}
if (b.jenis === "persegi") {
return b.sisi * b.sisi; // b: Persegi
}
const cek: never = b; // b: never
return cek;
}
| Baris | Type b |
|---|---|
| awal fungsi | Bentuk |
di dalam if (b.jenis === "lingkaran") |
Lingkaran |
di dalam if (b.jenis === "persegi") |
Persegi |
setelah dua if |
never |
Begitu b.jenis sama dengan "lingkaran", cuma Lingkaran yang cocok, jadi b.jari boleh dipakai. b.sisi di cabang yang sama ditolak, karena lingkaran gak punya sisi.
Cek lengkap dengan never
never adalah type yang gak punya nilai sama sekali. Setelah dua if tadi, semua jenis sudah ditangani, jadi b jadi never dan const cek: never = b; lolos.
Sekarang Segitiga ditambahkan ke union, tapi fungsi luas lupa diubah:
type Segitiga = { jenis: "segitiga"; alas: number; tinggi: number };
type Bentuk = Lingkaran | Persegi | Segitiga;
Baris const cek: never = b; langsung error: Type 'Segitiga' is not assignable to type 'never'. (ts(2322)). TypeScript menunjuk jenis yang belum kamu tangani, sebelum kodenya sempat jalan. Itu gunanya baris never: alarm untuk cabang yang terlupa.
⚠️ Jebakan yang sering ketemu
- Memakai
typeofuntuk array. Hasilnya"object". PakaiArray.isArray(...). - Lupa
returndi cabang pertama. Kalau cabangif (typeof input === "string")cumaconsole.logtanpareturn, setelahifituinputmasihstring | string[], karena dua jalan sama-sama sampai ke sana. - Mengecek nama class dengan
typeof.typeof tanggaluntukDatehasilnya"object". Pakaiinstanceof Date. - Mengira latihannya mengecek narrowing-mu. Di latihan,
return input.length;saja sudah memberi 5 dan 3, karena string dan array sama-sama punyalength. Tetap tulis pengecekannya: di kode sungguhan, dua cabang biasanya melakukan hal yang beda.
TL;DR: Type narrowing adalah TypeScript mempersempit type di dalam sebuah cabang dari pengecekan yang kamu tulis. typeof x === "string" untuk type dasar, Array.isArray(x) untuk array (karena typeof array itu "object"), "properti" in x untuk object dengan properti berbeda, x instanceof Date untuk nilai dari class, dan x.jenis === "..." untuk discriminated union. Setelah cabang yang return, sisanya otomatis jadi type yang tersisa. Kalau semua jenis sudah ditangani, sisanya never, dan const cek: never = x; jadi alarm untuk jenis yang terlupa.