Skip to main content

Що робить implements у TypeScript?

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 задовольняє обидва контракти. Ланцюжок прототипів залишається плоским. Додай третій інтерфейс - список зростає без жодної ієрархії успадкування. Ось у чому сенс: композиція без успадкування.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?