Vì TypeScript dùng structural typing (tương thích theo hình dạng), nhưng bổ sung thêm một luật riêng cho object literal gọi là excess property check.
type Options = { url: string }
const a: Options = { url: '/x', timeout: 5 } // error: timeout không có trong Options
const raw = { url: '/x', timeout: 5 }
const b: Options = raw // OKGiải thích:
- Theo structural typing, raw có đủ những gì Options yêu cầu, field thừa không phá vỡ tính tương thích → hợp lệ.
- Excess property check chỉ áp cho object literal gán trực tiếp (còn gọi là "fresh" literal). Đây là heuristic bắt lỗi gõ nhầm tên property, vì literal viết tay gần như luôn là ý định khai báo đúng hình dạng.
- Gán qua biến làm mất tính "fresh", nên chỉ còn kiểm tra structural.
Cách xử lý khi bạn thực sự muốn cho phép field thừa: thêm index signature [k: string]: unknown vào type, hoặc khai báo type rộng hơn — đừng dập bằng as.
Hệ quả của structural typing cần biết: hai type khác tên nhưng cùng hình dạng là thay thế được cho nhau, nên type UserId = string không ngăn được việc truyền nhầm OrderId. Muốn phân biệt phải dùng branded type:
type UserId = string & { readonly __brand: 'UserId' }Cách này tạo ra sự khác biệt về hình dạng, khiến compiler từ chối gán chéo dù runtime vẫn chỉ là chuỗi.