Структурна типізація (утка типізація) в TypeScript
Що таке структурна типізація?
TypeScript використовує структурну типізацію — типи сумісні на основі їх структури (форми/властивості), а не їх імені або оголошення. Якщо два типи мають однакову структуру, вони сумісні.
"Якщо воно йде, як качка, і крякає, як качка, то це, напевно, качка."
Основний приклад
typescript
interface Point {
x: number;
y: number;
}
// Не потрібне явне "implements Point"!
const point = { x: 10, y: 20, z: 30 };
function printPoint(p: Point): void {
console.log(`(${p.x}, ${p.y})`);
}
printPoint(point); // ✅ Працює! point має x та yСтруктурна vs Номінальна типізація
| Структурна (TypeScript) | Номінальна (Java, C#) |
|---|---|
| Типи співпадають за структурою | Типи співпадають за іменем/оголошенням |
Не потрібно implements | Потрібно явно реалізувати інтерфейс |
| Більш гнучка | Більш сувора |
typescript
// У TypeScript (структурна):
interface Cat { meow(): void }
interface CatLike { meow(): void }
const cat: Cat = { meow() {} };
const catLike: CatLike = cat; // ✅ Однакова структура = сумісні
// У Java (номінальна): це б не вдалося
// Cat і CatLike — це різні типи, навіть з однаковими методамиПеревірка надмірних властивостей
TypeScript має спеціальну сувору перевірку для літералів об'єктів (пряме присвоєння):
typescript
interface Config {
host: string;
port: number;
}
// ❌ Перевірка надмірних властивостей — літерал об'єкта
const config: Config = {
host: "localhost",
port: 3000,
debug: true // ❌ Помилка: 'debug' не існує в типі 'Config'
};
// ✅ Немає перевірки надмірних властивостей — через змінну
const obj = { host: "localhost", port: 3000, debug: true };
const config: Config = obj; // ✅ Працює (структурна сумісність)
// ✅ Немає перевірки надмірних властивостей — через аргумент функції
function startServer(config: Config) {}
startServer(obj); // ✅ ПрацюєСумісність класів
typescript
class Animal {
name: string;
constructor(name: string) { this.name = name; }
}
class Person {
name: string;
constructor(name: string) { this.name = name; }
}
// Структурно сумісні — однакова форма!
let animal: Animal = new Person("Alice"); // ✅
let person: Person = new Animal("Dog"); // ✅Коли структурна типізація викликає проблеми
typescript
type USD = number;
type EUR = number;
// ❌ Немає безпеки типів — обидва це просто числа
function convertToEUR(amount: USD): EUR {
return amount * 0.85;
}
const euros: EUR = 100;
convertToEUR(euros); // ✅ Немає помилки! (але логічно неправильно)Рішення: Брендовані типи
typescript
type USD = number & { __brand: "USD" };
type EUR = number & { __brand: "EUR" };
function usd(amount: number): USD {
return amount as USD;
}
function convertToEUR(amount: USD): EUR {
return (amount * 0.85) as EUR;
}
const dollars = usd(100);
convertToEUR(dollars); // ✅
convertToEUR(100 as EUR); // ❌ Помилка: EUR не можна присвоїти USDВажливо:
Структурна типізація робить TypeScript гнучким і сумісним з патернами JavaScript. Однак це означає, що типи з однаковою формою є взаємозамінними — використовуйте брендовані типи, коли вам потрібна поведінка, подібна до номінальної (наприклад, валюта, ідентифікатори користувачів тощо).
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.