Type narrowing adalah TypeScript yang menentukan tipe yang lebih spesifik untuk sebuah nilai di titik tertentu dalam kode, berdasarkan pemeriksaan yang sudah dilakukan kode itu. Parameter string | number menjadi string di dalam if (typeof x === "string") dan number di else.
Memanggil value.toFixed(2) sebelum pemeriksaan akan menjadi error kompilasi, karena toFixed tidak ada pada string. Pemeriksaannya adalah JavaScript biasa dan berjalan saat runtime; narrowing adalah compiler yang membaca pemeriksaan itu dan menyesuaikan tipenya. Tidak ada kode tambahan yang dihasilkan.
Control Flow Analysis
TypeScript mengikuti setiap jalur di dalam fungsi: if/else, return dan throw lebih awal, switch, perulangan, dan operator short-circuit &&, ||, ?? dan ?:. Di setiap titik, tipe sebuah variabel adalah apa pun yang masih mungkin di sana.
Gaya return lebih awal ("guard clause") adalah cara paling mudah dibaca untuk mempersempit tipe: tangani kasus yang tidak biasa lebih dulu, dan sisa fungsi bekerja dengan tipe yang sudah bersih.
Semua Cara Mempersempit Tipe
| Bentuk | Contoh | Mempersempit |
|---|---|---|
| typeof | typeof x === "string" | tipe primitif dan fungsi |
| Truthiness | if (x) | membuang null, undefined dan literal falsy |
| Kesamaan | x === "a", x == null, x !== undefined | literal, null, undefined |
in | "swim" in pet | union object, berdasarkan property |
| instanceof | err instanceof TypeError | instance class |
Array.isArray | Array.isArray(x) | array vs yang lainnya |
| Assignment | x = 5 | ke tipe nilai yang diberikan |
| Type predicate | function isUser(x: unknown): x is User | apa pun yang bisa kamu periksa |
| Assertion function | function assertUser(x: unknown): asserts x is User | semua kode setelah pemanggilan |
| Property diskriminan | switch (shape.kind) | tagged union |
Tiga yang terakhir dibahas di halaman type guard dan discriminated union. Sisanya ada di bawah.
Truthiness Narrowing
if (x) membuang null dan undefined (serta tipe literal false, 0, ""). Bentuk ini singkat, dan punya satu jebakan klasik: 0 dan "" bernilai falsy, jadi nilai yang valid dianggap tidak ada.
Untuk number dan string, bandingkan secara eksplisit dengan undefined atau null (atau pakai ??). Truthiness aman untuk object, array, dan fungsi, yang tidak pernah bernilai falsy.
Equality Narrowing
===, !==, == dan != mempersempit kedua sisi. Membandingkan dengan literal mempersempit ke literal itu; == null (kesamaan longgar) cocok dengan null dan undefined sekaligus dalam satu pemeriksaan, dan inilah satu-satunya tempat kesamaan longgar lazim dipakai.
Membandingkan dua variabel mempersempit keduanya ke tipe yang mungkin sama-sama mereka miliki: kalau a: string | number dan b: string | boolean lolos a === b, keduanya bertipe string di dalam if.
Operator in
"key" in obj mempersempit union dari beberapa object type ke anggota yang punya (atau mungkin punya) property itu.
in juga bekerja pada unknown setelah kamu tahu nilainya adalah object: setelah typeof v === "object" && v !== null && "id" in v, TypeScript tahu v punya property id bertipe unknown. Untuk union yang kamu rancang sendiri, property penanda bersama (kind: "fish") lebih jelas daripada mengecek keberadaan method: pola itu disebut discriminated union.
Assignment Narrowing
Sebuah variabel punya tipe yang dideklarasikan, dan tipe hasil narrowing yang mengikuti nilai yang diberikan kepadanya. Memberikan sebuah nilai mempersempit variabel itu ke tipe nilai tersebut, selama masih di dalam tipe yang dideklarasikan.
Kapan Narrowing Hilang
Narrowing bersifat lokal dan hati-hati. Beberapa situasi mengembalikannya ke awal:
- Ekspresi yang berbeda. Memeriksa
obj.namemempersempitobj.name(danobj["name"]), tetapi tidakobj[key]ketikakeyadalah variabelstringdan bukan literal, dan tidak juga salinan yang dibuat sebelum pemeriksaan. - Callback dan pemberian nilai baru. Di dalam callback,
letyang sudah dipersempit hanya mempertahankan narrowing-nya kalau tidak diberi nilai baru setelah callback dibuat.constatau parameter yang tidak pernah diberi nilai baru tetap terpersempit. - Pemeriksaan yang tersembunyi di fungsi bantu. Fungsi
isString(x: unknown): booleantidak memberi tahu compiler apa pun. Beri tipe kembalian berupa type predicate,x is string, dan pemanggilannya akan mempersempit tipe sepertitypeof.
Compiler melaporkan index.ts(5,38): error TS18048: 'x' is possibly 'undefined'. Callback itu bisa berjalan belakangan, setelah x = undefined. Hapus pemberian nilai terakhir itu (atau salin nilainya ke const di dalam if), maka kodenya lolos kompilasi dan mencetak 5 dua kali.
Fungsi bantu yang mengembalikan boolean bisa diperbaiki dengan menyatakan apa yang dibuktikannya. Itulah yang disebut type guard:
function isString(value: unknown): value is string {
return typeof value === "string";
}
Sejak TypeScript 5.5, compiler menyimpulkan predicate seperti itu untuk arrow function sederhana, dan karena itulah list.filter((x) => x !== undefined) sekarang mengembalikan array tanpa undefined.
Pertanyaan yang sering diajukan
Apa itu type narrowing di TypeScript?
Narrowing adalah TypeScript yang mempersempit tipe sebuah variabel di dalam sebuah blok berdasarkan pemeriksaan yang dilakukan kode. Setelah if (typeof x === "string"), string | number hanya menjadi string di dalam if dan hanya number di else. Compiler mengikuti if, else, return, switch, &&, || dan ?: untuk menentukan tipe di setiap titik, dan proses ini disebut control flow analysis.
Kenapa TypeScript tidak mempersempit tipeku?
Penyebab yang umum: pemeriksaannya dilakukan pada ekspresi yang berbeda dari yang kamu pakai (obj.a yang diperiksa, obj[key] yang dipakai dengan key bertipe string); nilainya adalah let yang diberi nilai baru setelah sebuah callback dibuat, sehingga callback itu kehilangan narrowing-nya; atau pemeriksaannya tersembunyi di fungsi bantu yang mengembalikan boolean biasa, bukan type predicate x is T.
Apakah type narrowing bekerja saat runtime?
Pemeriksaannya iya: typeof, instanceof, in dan === adalah JavaScript biasa yang berjalan. Narrowing-nya sendiri hanya terjadi saat kompilasi. TypeScript membaca pemeriksaan runtime-mu dan menyesuaikan tipe statis agar cocok, dan tidak ada apa pun yang ditambahkan ke JavaScript yang dihasilkan.
Bagaimana cara mempersempit tipe unknown di TypeScript?
Dengan pemeriksaan yang sama: typeof value === "string", Array.isArray(value), value instanceof Date, atau untuk object typeof value === "object" && value !== null && "id" in value. Untuk pemeriksaan yang bisa dipakai ulang, tulis fungsi type guard dengan tipe kembalian value is T.
Bagaimana cara membuang undefined dari array di TypeScript?
Sejak TypeScript 5.5, items.filter((x) => x !== undefined) mengembalikan T[] tanpa undefined, karena compiler menyimpulkan callback itu sebagai type predicate. Di versi yang lebih lama, tulis predicate-nya sendiri: items.filter((x): x is T => x !== undefined).