Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що робить implements у TypeScript?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)`implements` у TypeScript змушує клас відповідати формі інтерфейсу - це перевіряється під час компіляції. ```ts interface Shape { area(): number; } class Circle implements Shape { constructor(private r: number) {} area() { return Math.PI * this.r ** 2; } } ``` **Ключове:** на відміну від `extends`, `implements` не копіює код. Перевіряє лише структуру. Інтерфейси зникають у JS-виводі.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`implements`** у TypeScript - це контракт часу компіляції: клас повинен відповідати кожному члену інтерфейсу, інакше компілятор зупиниться. ## Теорія ### TL;DR - Уяви список перевірки від санітарної інспекції. Інтерфейс - це вимоги, клас - кухня, яку перевіряють. - `implements` перевіряє форму, а не поведінку. Нічого з інтерфейсу не копіюється в клас. - Використовуй, коли потрібна структурна гарантія без успадкування. - `class Foo implements A, B` повинен задовольнити обидва інтерфейси. - Жодних витрат у рантаймі: інтерфейси зникають при компіляції. Node бачить звичайний JS. ### Швидкий приклад ```ts interface Shape { area(): number; } class Circle implements Shape { constructor(private radius: number) {} area() { return Math.PI * this.radius ** 2; // Вимагається Shape } } ``` Прибери `area()` - TypeScript повідомить: `Property 'area' is missing in type 'Circle' but required in type 'Shape'`. У скомпільованому JS інтерфейсу немає жодного сліду. ### implements vs extends `extends` копіює методи батьківського класу через ланцюжок прототипів. `implements` лише перевіряє форму, жоден код не передається. Саме тому клас може реалізувати кілька інтерфейсів (`implements A, B`), але розширити - тільки один батьківський клас. Обирай `implements`, коли потрібен структурний контракт без прив'язки до ланцюжка прототипів. Обирай `extends`, коли потрібен спільний код. ### Коли використовувати - API плагінів: `class MyPlugin implements Plugin` змушує користувачів відповідати контракту - NestJS: `class AuthGuard implements CanActivate` - Тестування: гарантує, що мок-класи мають усі методи реального класу - Кілька типів поведінки: `class Duck implements Flyable, Swimmable` - Redux: `class IncrementAction implements Action` ### Як це обробляє компілятор Під час `tsc` TypeScript обходить AST класу та порівнює кожен член інтерфейсу з визначенням класу. Відсутній член або невідповідна сигнатура - компілятор видає помилку. У JS-вивід нічого з цього не потрапляє. У рантаймі V8 бачить звичайний синтаксис класів без жодних метаданих про інтерфейси. ### Типові помилки **Очікування, що `implements` скопіює реалізацію** ```ts interface HasGreet { greet(): string; } class Bot implements HasGreet { // Помилка: Property 'greet' is missing } ``` Бачив це на код-рев'ю: хтось додає `implements` і чекає, що метод з'явиться сам. Не з'явиться. Пишемо самостійно. **Використання `implements` з конкретним класом замість `extends`** ```ts class Animal { move() {} } class Dog implements Animal { // Помилка: Property 'move' is missing bark() {} } ``` Щоб успадкувати `move()`, використовуй `extends Animal`. З `implements` кожен член потрібно визначати вручну, навіть якщо цільовий тип - клас, а не інтерфейс. **Невідповідність модифікаторів доступу** ```ts interface User { getName(): string; } class Admin implements User { private getName() { return "admin"; } // Помилка: private порушує публічний контракт } ``` Інтерфейси описують публічний API. Метод зі специфікатором `private` не задовольнить вимогу інтерфейсу. Прибери `private` або явно постав `public`. **Забутий `?` у необов'язкових членах інтерфейсу** ```ts interface Swimmable { swim?(): void; // Необов'язково в інтерфейсі } class Duck implements Swimmable { swim() { console.log("Paddle!"); } // Нормально - optional означає, що можна і пропустити } ``` Прибери `?` з інтерфейсу - і `swim()` стає обов'язковим для кожного класу, який реалізує `Swimmable`. Легко пропустити під час рефакторингу. ### Де зустрічається в реальному коді - NestJS: `class AuthGuard implements CanActivate`, `class Logger implements NestInterceptor` - Express: класи middleware, що відповідають сигнатурі middleware-функції - React: мок-реалізації у Jest, які відповідають реальному API компонента - Vue 3: `class MyPlugin implements Plugin` - Redux: `class IncrementAction implements Action` ### Питання на співбесіді **Q:** Яка різниця між `implements` і `extends`? **A:** `extends` копіює методи через ланцюжок прототипів і працює тільки з класами. `implements` перевіряє форму і працює тільки з інтерфейсами. Клас може розширювати один батьківський клас, але реалізовувати - кілька інтерфейсів. **Q:** Чи може `implements` перевіряти приватні члени? **A:** Ні. Інтерфейси описують лише публічну форму. Метод зі специфікатором `private` не задовольнить вимогу інтерфейсу. **Q:** Що станеться, якщо два реалізованих інтерфейси мають члени з однаковою назвою, але різними типами повернення? **A:** TypeScript вимагає задоволення обох контрактів. Конфліктні типи повернення дадуть помилку компіляції. Автоматичного вирішення немає. **Q:** Як `implements` взаємодіє зі злиттям декларацій (declaration merging)? **A:** Якщо інтерфейс оголошено двічі, TypeScript об'єднує їх в одну структуру. Клас повинен задовольнити обидва оголошення. Оголоси `Foo` з методом `a()`, потім знову з `b()` - і `implements Foo` вимагатиме обох. ## Приклади ### Базовий: контракт форми ```ts interface Shape { area(): number; perimeter(): number; } class Rectangle implements Shape { constructor(private width: number, private height: number) {} area() { return this.width * this.height; } perimeter() { return 2 * (this.width + this.height); } } const rect = new Rectangle(4, 5); console.log(rect.area()); // 20 console.log(rect.perimeter()); // 18 ``` Обидва методи вимагає інтерфейс. Прибери один - `tsc` зупинить збірку з точним повідомленням про відсутній член. ### Середній рівень: кілька інтерфейсів ```ts interface Flyable { fly(): void; } interface Loggable { log(message: string): void; } class Drone implements Flyable, Loggable { fly() { console.log("Drone is airborne"); } log(message: string) { console.log(`[Drone] ${message}`); } } const drone = new Drone(); drone.fly(); // "Drone is airborne" drone.log("Battery at 80%"); // "[Drone] Battery at 80%" ``` `Drone` задовольняє обидва контракти. Ланцюжок прототипів залишається плоским. Додай третій інтерфейс - список зростає без жодної ієрархії успадкування. Ось у чому сенс: композиція без успадкування.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.