TypeScript: Menangkap Kesalahan Sebelum Sampai Produksi · 4/8
Union dan narrowing
Memodelkan "salah satu dari ini" — hal paling berguna yang TypeScript lakukan untuk data nyata.
Baca 30 menit
Setelah pelajaran ini kamu bisa
- Memodelkan "salah satu dari ini" dengan union type
- Mempersempit sebuah union dengan aman lewat pemeriksaan
- Memakai discriminated union untuk state machine
- Membuat compiler memaksamu menangani setiap kasus
Union menyatakan sebuah value adalah salah satu dari beberapa type: string | number. Ini hal paling berguna yang TypeScript lakukan untuk data nyata, karena data nyata penuh dengan "entah ini atau itu".
// A value that may legitimately be absent - extremely common.
let venue: Venue | undefined;
// A fixed set of allowed strings, checked by the compiler.
type Role = "lead" | "learner";
let role: Role = "lead";
role = "admin"; // Error: Type '"admin"' is not assignable to type 'Role'
// Unions of object shapes.
type Result = { ok: true; value: string } | { ok: false; error: string };Narrowing
Kamu tidak bisa memakai sebuah union sampai kamu tahu anggota mana yang kamu punya. Narrowing adalah membuktikannya dengan pemeriksaan runtime biasa — dan TypeScript memahami pemeriksaan yang memang sudah kamu tulis di JavaScript.
function format(value: string | number) {
value.toFixed(2);
// Error: Property 'toFixed' does not exist on type 'string | number'.
if (typeof value === "number") {
return value.toFixed(2); // narrowed to number
}
return value.trim(); // narrowed to string - nothing else is left
}
function greet(venue: Venue | undefined) {
// Guard clause: handle the absent case and return.
if (!venue) return "No venue selected";
return venue.name; // narrowed to Venue below this line
}| Pemeriksaan | Mempersempit |
|---|---|
typeof x === "string" | Primitive |
Array.isArray(x) | Array |
x instanceof Error | Class |
"field" in x | Bentuk object berdasarkan property |
x === null, if (!x) | Membuang null dan undefined |
x.kind === "circle" | Discriminated union |
Discriminated union
Beri setiap anggota union sebuah field literal yang sama — biasanya kind, type, atau status — dan TypeScript bisa membedakannya dari satu pemeriksaan saja. Ini pola yang harus kamu pakai setiap kali sesuatu punya beberapa state.
type RequestState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: Venue[] }
| { status: "error"; message: string };
function render(state: RequestState): string {
switch (state.status) {
case "idle":
return "Press search";
case "loading":
return "Loading...";
case "success":
// `data` exists here, and ONLY here.
return state.data.length + " venues found";
case "error":
// `message` exists here, and only here.
return "Failed: " + state.message;
}
}loading yang membawa data usang, atau success yang punya pesan error. Kombinasi yang tidak sah tidak bisa dibuat.Pola yang sama dalam JavaScript biasa. Bentuknya itulah yang bekerja; TypeScript hanya memastikan kamu melakukannya dengan benar.
Hasil
Tekan Jalankan untuk melihat hasilnya.
Ini jalan di browser kamu, di dalam sandbox. Apa pun yang kamu tulis di sini tidak bisa merusak situs.
Keterlengkapan: membuat compiler mengingatkanmu
Ini hasilnya. Tambahkan state kelima ke union-nya dan setiap switch yang melewatkannya jadi error compile — kalau kamu menyiapkannya dengan never.
function assertNever(value: never): never {
throw new Error("Unhandled case: " + JSON.stringify(value));
}
function render(state: RequestState): string {
switch (state.status) {
case "idle": return "Press search";
case "loading": return "Loading...";
case "success": return state.data.length + " venues";
case "error": return "Failed: " + state.message;
default:
// If every case above is handled, `state` is `never` here, and this
// compiles. Add a new state to the union without adding a case, and
// `state` is that new type - which is not assignable to `never`.
return assertNever(state);
}
}{ status: "cancelled" } ke union-nya sekarang menghasilkan: Argument of type '{ status: "cancelled"; }' is not assignable to parameter of type 'never'. Compiler-nya baru saja menemukan setiap tempat yang perlu kamu perbarui.Tugas praktik
Di scratch.ts, definisikan type Shape = { kind: "circle"; radius: number } | { kind: "rect"; width: number; height: number } dan tulis area(shape: Shape): number memakai switch dengan default assertNever. Pastikan lolos compile. Sekarang tambahkan | { kind: "triangle"; base: number; height: number } ke Shape lalu jalankan bunx tsc --noEmit — baca error yang diberikannya.
Hasil yang diharapkan
Error yang menyatakan type triangle tidak bisa ditugaskan ke 'never'.
Cek pemahaman
Pilih jawaban dulu