TypeScript: Menangkap Kesalahan Sebelum Sampai Produksi · 3/8
Structural typing: bentuk, bukan nama
TypeScript peduli pada bentuk object, bukan namanya. Ini mengejutkan orang yang datang dari Java.
Baca 22 menit
Setelah pelajaran ini kamu bisa
- Menjelaskan kenapa TypeScript membandingkan bentuk, bukan nama
- Memprediksi kapan sebuah object bisa ditugaskan ke sebuah type
- Menjelaskan kenapa property tambahan kadang error dan kadang tidak
- Memakai branded type saat bentuk saja tidak cukup spesifik
TypeScript bertanya "apakah value ini punya bentuk yang benar?", bukan "apakah value ini dideklarasikan sebagai type itu?". Ini disebut structural typing, dan mengejutkan semua orang yang datang dari Java atau C#.
interface Point {
x: number;
y: number;
}
// This object never mentions Point, and never needs to.
const somewhere = { x: 1, y: 2 };
function distanceFromOrigin(p: Point) {
return Math.sqrt(p.x * p.x + p.y * p.y);
}
distanceFromOrigin(somewhere); // fine - the shape matches
// Two unrelated interfaces with the same shape are interchangeable.
interface Coordinate { x: number; y: number }
const c: Coordinate = somewhere;
const p: Point = c; // also fine - names are irrelevantInilah sebabnya type di proyek ini mendeskripsikan data alih-alih membungkusnya dalam class. Object biasa yang punya field yang benar *memang* sebuah LearningModule, tanpa upacara apa pun.
Structural typing mencerminkan perilaku JavaScript sebenarnya saat runtime — ia hanya peduli apakah property-nya ada.
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.
Property tambahan: aturan yang membingungkan
Punya property lebih banyak dari yang dibutuhkan biasanya tidak masalah — bentuknya tetap cocok. Tapi object literal yang ditulis *langsung* di tempat pemanggilan mendapat pemeriksaan tambahan, karena di situ property berlebih hampir selalu salah ketik.
interface Options {
perPage: number;
}
function search(options: Options) {}
// 1. A variable with extra properties: allowed.
const settings = { perPage: 10, theme: "dark" };
search(settings); // fine
// 2. The same object written inline: rejected.
search({ perPage: 10, theme: "dark" });
// ~~~~~
// Object literal may only specify known properties,
// and 'theme' does not exist in type 'Options'.{ perPage: 10, thmee: "dark" } — salah tulis yang tanpanya akan diabaikan diam-diam.Saat sebuah bentuk kurang spesifik
Structural typing punya kelemahan nyata: dua hal dengan bentuk sama bisa saling menggantikan bahkan saat mereka tidak boleh tertukar. Id user dan id venue keduanya number.
// The bug the compiler cannot see:
function cancelBooking(bookingId: number, userId: number) {}
cancelBooking(userId, bookingId); // arguments swapped - compiles fine
// A "branded" type makes them incompatible on purpose.
type BookingId = number & { readonly __brand: "BookingId" };
type UserId = number & { readonly __brand: "UserId" };
function cancel(bookingId: BookingId, userId: UserId) {}
const b = 1 as BookingId;
const u = 2 as UserId;
cancel(b, u); // fine
cancel(u, b); // Error: 'UserId' is not assignable to 'BookingId'Tugas praktik
Di scratch.ts, tulis interface Named { name: string } dan sebuah function yang menerima Named. Kirimkan: sebuah variable berisi { name: "a", extra: 1 }, lalu object yang sama secara inline. Pastikan satu lolos compile dan satu tidak, lalu tulis satu kalimat kenapa.
Hasil yang diharapkan
Variable-nya diterima; literal inline memicu excess property checking.