Intersection Types & keyof — TypeScript

Intersection type (&) menggabungkan beberapa type menjadi satu — objek harus memiliki semua property dari semua type. type HasName = { nama: string } type…

Intersection type, ditulis dengan &, menggabungkan beberapa tipe jadi satu: nilainya harus memenuhi semua tipe itu sekaligus. keyof mengambil nama-nama properti sebuah tipe sebagai union. Bayangkan formulir pendaftaran yang terdiri dari dua lembar yang dijepret jadi satu: lembar posisi dan lembar warna. Formulirnya baru sah kalau dua lembar itu terisi semua. Daftar nama kolom di formulir itu adalah keyof-nya.

🧪 Tebakan cepat: type Titik = Posisi & Warna. Apakah objek { x: 1, y: 2 } lolos sebagai Titik? Kalau & diganti |? Tebak dulu, jawabannya ada di bagian "& lawan |".

Gabungkan dua tipe dengan &

type Posisi = {
  x: number;
  y: number;
};

type Warna = {
  warna: string;
};

type Titik = Posisi & Warna; // harus punya x, y, dan warna

const titik: Titik = { x: 1, y: 2, warna: "merah" }; // lolos
const tanpaWarna: Titik = { x: 1, y: 2 };            // error
error TS2322: Type '{ x: number; y: number; }' is not assignable to type 'Titik'.
  Property 'warna' is missing in type '{ x: number; y: number; }' but required in type 'Warna'.

& lawan |

Union | (sudah kamu kenal) artinya "salah satu". Intersection & artinya "semuanya".

Objek Posisi & Warna Posisi | Warna
{ x: 1, y: 2, warna: "merah" } lolos lolos
{ x: 1, y: 2 } error, warna kurang lolos sebagai Posisi
{ warna: "merah" } error, x dan y kurang lolos sebagai Warna
{ x: 1 } error, y kurang error, y kurang

Jadi { x: 1, y: 2 } ditolak sebagai Posisi & Warna, tapi lolos sebagai Posisi | Warna karena sudah lengkap sebagai Posisi. Itu jawaban tebakannya.

Hati-hati kalau dua tipe punya properti bernama sama dengan tipe berbeda. Di type A = { x: number } & { x: string }, x harus number sekaligus string. Gak ada nilai seperti itu, jadi A["x"] bertipe never, tipe yang gak punya nilai sama sekali.

keyof dan T[K]

keyof mengubah sebuah tipe jadi union nama-nama propertinya:

type Kunci = keyof Titik;      // "x", "y", atau "warna"
type TipeWarna = Titik["warna"]; // string
type TipeX = Titik["x"];         // number

const k: Kunci = "warna"; // lolos

getValue: kunci yang pasti ada

Gabungkan keyof dengan generic, dan kamu dapat fungsi yang cuma menerima kunci yang benar-benar ada:

function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

getValue({ x: 1, y: 2, warna: "merah" }, "warna"); // tipe string, nilai "merah"
getValue({ x: 5, y: 10, warna: "biru" }, "x");     // tipe number, nilai 5
getValue({ x: 1, y: 2, warna: "merah" }, "z");     // error
error TS2345: Argument of type '"z"' is not assignable to parameter of type '"warna" | "x" | "y"'.

⚠️ Jebakan yang sering ketemu

TL;DR: A & B (intersection) artinya nilainya harus punya semua properti A dan semua properti B: Titik = Posisi & Warna wajib punya x, y, dan warna. A | B (union) cukup salah satunya. Properti sama dengan tipe bertabrakan jadi never. keyof T adalah union nama properti T, dan T[K] adalah tipe properti K. getValue<T, K extends keyof T>(obj: T, key: K): T[K] mengembalikan obj[key] dengan tipe yang tepat, dan menolak kunci yang gak ada seperti "z".

Yang akan kamu pelajari