narrowingtype-guardpredicateMenengah3 mnt baca

Type Predicate

Fungsi boolean custom yang mempersempit tipe: x is T.

Kenapa predicate itu ada

Narrowing bawaan (typeof, in, instanceof) cuma ngerti struktur dangkal. Tapi validasi dunia nyata sering butuh logika domain: "apakah objek ini User yang valid?", "apakah string ini email?", "apakah array ini sudah bersih dari undefined?". Type predicate (x is T) adalah cara kamu mengajari compiler pengecekan custom: fungsi boolean biasa yang return-nya sekaligus janji ke compiler, "kalau true, maka x bertipe T".

Alur validasi API response langkah demi langkah

Skenario nyata: fetch mengembalikan data yang tipenya tidak kamu ketahui. Kamu mau mengubahnya jadi User yang terverifikasi, tanpa berbohong ke compiler.

typescript
interface User { id: number; nama: string; email: string }

// Langkah 1: tulis predicate. Return type-nya "x is User", BUKAN boolean.
function adalahUser(x: unknown): x is User {
  if (typeof x !== "object" || x === null) return false;
  const o = x as Record<string, unknown>; // cek struktur, bukan klaim tipe akhir
  return (
    typeof o.id === "number" &&
    typeof o.nama === "string" &&
    typeof o.email === "string"
  );
}

async function ambilUser(id: number): Promise<User | null> {
  const res = await fetch(`/api/user/${id}`);
  const raw: unknown = await res.json(); // Langkah 2: anggap unknown dulu.

  // Langkah 3: predicate mempersempit unknown jadi User. Tanpa casting!
  if (adalahUser(raw)) {
    return raw; // raw: User, terverifikasi saat runtime
  }
  return null; // data rusak, tolak dengan sopan
}

Tanpa predicate, kamu cuma punya dua pilihan buruk: as User (berbohong ke compiler, dibahas di modul assertion) atau menulis if validasi yang panjang dan tidak reusable di setiap tempat.

Predicate untuk filter array: kasus paling laris

typescript
const campur: (string | undefined)[] = ["a", undefined, "b"];

function bukanUndefined(x: string | undefined): x is string {
  return x !== undefined;
}

const bersih: string[] = campur.filter(bukanUndefined);

Tanpa predicate, campur.filter(x => x !== undefined) hasilnya tetap (string | undefined)[]. Error-nya muncul belakangan, saat elemennya dipakai:

typescript
function kirimSemua(daftar: string[]) { /* ... */ }
kirimSemua(campur.filter(x => x !== undefined));
text
error TS2345: Argument of type '(string | undefined)[]' is not assignable
  to parameter of type 'string[]'.

Predicate membuat .filter() "mengerti" tipe hasil saringannya. Ini trik yang dipakai di hampir semua codebase TypeScript yang matang.

Kapan dipakai di project nyata

  • Validasi data eksternal: response API, JSON.parse, pesan WebSocket, isi localStorage. Semua mulai dari unknown, predicate yang mengubahnya jadi tipe domain dengan bukti runtime.
  • Filter koleksi: buang null, undefined, atau item invalid dari array sebelum diolah, dengan tipe hasil yang bersih tanpa sisa union.
  • Type guard yang reusable: daripada mengulang typeof x === "object" && x !== null && ... di sepuluh tempat, bungkus sekali jadi adalahX(x) dan pakai di mana-mana.

Catatan teknis: Predicate adalah klaim yang dipercaya compiler tanpa verifikasi. Kalau logikanya salah (return true untuk yang bukan User), type safety jebol diam-diam dan tidak ada yang protes. Tulis predicate dengan hati-hati, dan unit test predicate yang kritis seperti kamu mengetes fungsi bisnis.

Tantangan

Filter nilai valid

Buat type predicate adalahAngka(x: unknown): x is number yang true hanya untuk number bukan NaN. Pakai untuk memfilter [1, "a", 2, NaN, 3] menjadi number[] berisi [1, 2, 3].

typescript
function adalahAngka(x: unknown): x is number {
  // true bila typeof number dan bukan NaN
}

const data: unknown[] = [1, "a", 2, NaN, 3];
const angka: number[] = data.filter(adalahAngka);
console.log(angka); // [1, 2, 3]