TypeScript перевіряє типи структурно: об'єкт підходить до типу, якщо має всі потрібні властивості потрібних типів. Зайві властивості цьому не заважають.
type Point = { x: number; y: number };
const point3d = { x: 1, y: 2, z: 3 };
const p: Point = point3d; // ок - є x і y, z просто ігнорується
Але для «свіжого» об'єкта-літерала діє додаткова перевірка зайвих властивостей:
const p: Point = { x: 1, y: 2, z: 3 };
// помилка: Object literal may only specify known properties, and 'z' does not exist in type 'Point'
Чому так. Якщо ви пишете літерал прямо там, де очікується конкретний тип, зайва властивість майже напевно - друкарська помилка або непорозуміння:
createUser({ name: 'Оля', emial: 'olia@example.com' }); // emial замість email - зловлено
Але якщо об'єкт уже існує й має ширшу форму, передати його туди, де потрібна частина полів, - нормальна практика структурної типізації.
Де перевірка зайвих властивостей не спрацьовує:
- об'єкт спершу присвоєно змінній, а потім передано;
- результат функції, розгортання (
{ ...defaults, extra: 1 }перевіряється, а от{ ...obj }, деobjмає зайве, - ні); - тип має індексну сигнатуру (
[key: string]: unknown) - тоді будь-які ключі допустимі; - приведення через
as.
Пастка з опціональними полями:
type Options = { timeout?: number; retries?: number };
const userOptions = { timeout: 500, retires: 3 }; // друкарська помилка в retries
const options: Options = userOptions; // жодної помилки! усі поля опціональні
Тип, у якого всі поля необов'язкові («слабкий тип»), TypeScript частково захищає: якщо в об'єкта немає жодного спільного поля з типом ({ timout: 500 }), буде помилка. Але одна правильна властивість поруч з друкарською помилкою - і перевірка мовчить, а retries тихо лишається не заданим.
Що робити: передавати літерали напряму в місця з типом, використовувати satisfies для об'єктів-конфігурацій, а для зовнішніх даних - валідацію під час виконання (Zod), яка може відкидати невідомі ключі (.strict()).