Intersection adalah operasi menggabungkan dua tipe atau lebih menjadi satu tipe yang memenuhi seluruh tipe penyusunnya sekaligus. Operator penggabungannya adalah tanda &, sehingga penulisan A & B berarti sebuah nilai harus berbentuk A dan sekaligus berbentuk B. Topik ini membahas cara menggabungkan bentuk objek dengan &, perilaku compiler ketika properti penyusun bertabrakan, serta kondisi ketika hasil penggabungan menyusut menjadi tipe never.
Menggabungkan bentuk dengan tanda &#
Tipe hasil intersection hanya menerima nilai yang memenuhi semua tipe penyusunnya, sedangkan union cukup dipenuhi salah satu penyusunnya. Karena itu & sering dipakai untuk merangkai kelompok properti yang dikelola secara terpisah, misalnya data inventaris dan data lokasi penyimpanan pada gudang. Contoh berikut menggabungkan dua interface menjadi satu tipe Barang.
interface Inventaris {
nama: string;
jumlah: number;
}
interface Lokasi {
rak: string;
tingkat: number;
}
type Barang = Inventaris & Lokasi;
const benda: Barang = {
nama: "modul sensor",
jumlah: 4,
rak: "A2",
tingkat: 3,
};
function label(b: Barang): string {
return `${b.jumlah} unit ${b.nama} di rak ${b.rak} tingkat ${b.tingkat}`;
}
console.log(label(benda));4 unit modul sensor di rak A2 tingkat 3Objek benda wajib memuat seluruh properti dari Inventaris maupun Lokasi, yaitu empat properti sekaligus, sebab nilai yang kekurangan satu properti saja tidak lagi memenuhi Inventaris & Lokasi. Sebagai imbalannya, semua properti itu dapat diakses tanpa pemeriksaan tambahan, sebagaimana fungsi label membaca nama, jumlah, rak, dan tingkat dalam satu tanda tangan. Nilai bertipe Barang juga sah dipakai di mana pun parameter Inventaris diharapkan, karena bentuknya mencakup seluruh properti tipe tersebut.
Pola ini memisahkan tiap kelompok data dengan rapi, sebab setiap kelompok properti tetap dideklarasikan di tempatnya masing-masing, sedangkan tipe hasilnya tinggal merangkainya. Menambah kelompok baru cukup dilakukan dengan menambahkan satu penyusun pada intersection, dan seluruh fungsi yang mengakses hasilnya otomatis menuntut properti baru tersebut. Dengan begitu, penambahan kemampuan pada sebuah tipe tidak mengubah deklarasi yang sudah ada.
Konflik properti dan penyusutan ke never#
Properti yang muncul pada dua penyusun tidak dipilih salah satu tipe begitu saja, melainkan memperoleh irisan dari kedua tipenya. Irisan dua tipe yang kompatibel menghasilkan tipe yang lebih sempit, misalnya number & 24 yang berlaku hanya untuk angka 24. Apabila kedua tipe tersebut tidak memiliki nilai yang sama, irisannya kosong dan menjadi never, yaitu tipe yang tidak memiliki satu nilai pun.
Irisan tidak selalu berakhir kosong. Apabila kedua penyusun menuntut nilai yang sama, irisan tetap berisi tipe tersebut, misalnya "aktif" & "aktif" yang menghasilkan "aktif". Penyusutan ke never terjadi tepat ketika tidak ada satu nilai pun yang memenuhi kedua sisi sekaligus. Dengan kata lain, never adalah hasil akhir dari pemotongan tipe yang makin sempit.
type Expect<T extends true> = T;
type Equal<X, Y> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? true : false;
type Tabrak = { status: "aktif" } & { status: "nonaktif" };
type Potongan = number & string;
const bukti1: Expect<Equal<Tabrak["status"], never>> = true;
const bukti2: Expect<Equal<Potongan, never>> = true;
console.log(bukti1, bukti2);true true# bukti tipe hanya sah bila pemeriksaan tsc lolos
tsc --noEmit --pretty false konflik-tipe.ts && echo "pemeriksaan tipe lolos"pemeriksaan tipe lolosTipe Tabrak menggabungkan dua penyusun yang sama-sama memiliki properti status dengan tipe literal yang berbeda. Akses terindeks (indexed access type) berupa Tabrak["status"] membaca tipe properti tersebut secara langsung. Pola Expect dengan Equal memastikan bahwa hasilnya benar-benar never, karena pemeriksaan tipe gagal bila dugaannya keliru. Properti status pada Tabrak kini tidak dapat menampung apa pun, termasuk "aktif" yang tampak masih wajar.
Menuliskan nilai pada tipe yang bertabrakan#
Penyusutan ke never baru terasa pada saat nilai dituliskan, ketika compiler menolak setiap usulan nilai untuk properti yang bertabrakan. Berkas berikut menuliskan objek yang menyertakan status: "aktif" pada tipe yang sudah menyusut, lalu mencoba membaca properti tersebut. Dua tindakan itu menghasilkan dua pesan error yang berbeda, sehingga keduanya dapat diperiksa satu per satu.
// tidak-dijalankan
type Gabung = { status: "aktif" } & { status: "nonaktif" };
const g: Gabung = { status: "aktif" };
console.log(g.status);# berkas ini sengaja tidak lolos pemeriksaan tipe
tsc --noEmit --pretty false konflik-salah.tskonflik-salah.ts(4,21): error TS2322: Type 'string' is not assignable to type 'never'.
konflik-salah.ts(6,15): error TS2339: Property 'status' does not exist on type 'never'.
The intersection 'Gabung' was reduced to 'never' because property 'status' has conflicting types in some constituents.Pesan error pertama menolak penugasan karena sasarannya bertipe never, sehingga tidak ada nilai string yang dapat mengisinya. Pesan error kedua menyebutkan penyebabnya secara eksplisit, yaitu intersection Gabung menyusut menjadi never karena properti status memiliki tipe yang saling bertentangan pada penyusunnya. Compiler tidak memilih tipe penyusun terakhir atau yang pertama, melainkan memotong kedua tipe tersebut sampai habis. Penolakan terjadi pada saat kompilasi, sehingga konflik tidak pernah sampai ke program yang berjalan.
Kesalahan umum#
Kekeliruan pertama adalah memperlakukan & sebagaimana |, yaitu mengira hasilnya boleh memenuhi salah satu penyusun saja. Padahal A & B justru lebih ketat daripada A maupun B sendiri, sehingga objek yang hanya memenuhi satu penyusun akan ditolak. Bila maksudnya adalah kebebasan memilih, gunakan union dan tuliskan A | B.
Kekeliruan kedua adalah mengira konflik properti diselesaikan dengan tipe penyusun yang ditulis terakhir. Compiler tidak berperilaku demikian, karena kedua tipe dipotong menjadi irisan, dan irisan yang kosong menjadi never. Perbaikannya adalah menyamakan tipe properti pada kedua penyusun, mengubah nama salah satu propertinya, atau memisahkan kedua bentuk menjadi anggota union apabila keduanya memang tidak dimaksudkan untuk dipakai bersama.
Kekeliruan ketiga adalah menggabungkan dua tipe yang masing-masing dimaksudkan untuk keadaan yang berbeda, misalnya Suhu & Kelembapan padahal kedua pengukuran itu jarang muncul bersamaan. Penggabungan paksa semacam ini membuat setiap penulisan nilai menjadi berat, dan biasanya menjadi tanda bahwa union lebih sesuai. Perbaikannya adalah meninjau hubungan antara kedua bentuk terlebih dahulu, sebelum operator penggabungannya dituliskan.
Ringkasan#
- Operator
&menghasilkan tipe yang memenuhi semua penyusunnya, sehingga properti setiap penyusun dapat diakses pada nilainya. - Properti yang muncul pada dua penyusun memperoleh irisan tipe keduanya, dan irisan yang tidak memiliki nilai bersama menyusut menjadi
never. - Tipe
nevertidak memiliki anggota, sehingga penulisan nilai apa pun pada properti yang menyusut keneverditolak compiler. - Intersection menambahkan syarat, bukan pilihan, sehingga union
|lebih tepat dipakai bila maksudnya memilih salah satu bentuk.
Referensi#
- Objects — The TypeScript Handbook — Memuat bagian Intersection Types yang membahas penggabungan tipe dan konfliknya.
- Unions and Intersection Types — The TypeScript Handbook
— Membandingkan operator
|dan&beserta contoh pemakaiannya.