Menu

Type Narrowing TypeScript: Cara Kerja Control Flow Analysis

Narrowing adalah cara TypeScript mengubah tipe yang lebar seperti string | number menjadi tipe yang spesifik di dalam if, switch, atau setelah return lebih awal. Semua bentuk narrowing di satu tempat: typeof, truthiness, kesamaan, in, instanceof, assignment, type predicate, dan discriminated union.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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

BentukContohMempersempit
typeoftypeof x === "string"tipe primitif dan fungsi
Truthinessif (x)membuang null, undefined dan literal falsy
Kesamaanx === "a", x == null, x !== undefinedliteral, null, undefined
in"swim" in petunion object, berdasarkan property
instanceoferr instanceof TypeErrorinstance class
Array.isArrayArray.isArray(x)array vs yang lainnya
Assignmentx = 5ke tipe nilai yang diberikan
Type predicatefunction isUser(x: unknown): x is Userapa pun yang bisa kamu periksa
Assertion functionfunction assertUser(x: unknown): asserts x is Usersemua kode setelah pemanggilan
Property diskriminanswitch (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.name mempersempit obj.name (dan obj["name"]), tetapi tidak obj[key] ketika key adalah variabel string dan bukan literal, dan tidak juga salinan yang dibuat sebelum pemeriksaan.
  • Callback dan pemberian nilai baru. Di dalam callback, let yang sudah dipersempit hanya mempertahankan narrowing-nya kalau tidak diberi nilai baru setelah callback dibuat. const atau parameter yang tidak pernah diberi nilai baru tetap terpersempit.
  • Pemeriksaan yang tersembunyi di fungsi bantu. Fungsi isString(x: unknown): boolean tidak memberi tahu compiler apa pun. Beri tipe kembalian berupa type predicate, x is string, dan pemanggilannya akan mempersempit tipe seperti typeof.

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).

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai