Angularшлях до Strong Junior
Модулі / JavaScript: основа / this і класи
🔥 0

Фундамент Ф1 · урок 8 з 12

this і класи

Компонент Angular — це клас. Сервіс — клас. У коді на кожному кроці this.tasks(), this.selected.emit(…), this.form.value. Цей урок пояснює, що таке клас, хто такий this і чому він іноді «губиться» — найпідступніша помилка, з якою стикаються всі, хто приходить в Angular.

Клас — креслення для обʼєктів

Коли обʼєктів одного виду багато — задачі, користувачі, — їх зручно створювати за одним кресленням. Клас і є таким кресленням: які поля в обʼєкта і що він уміє.

class Task {
  constructor(title) {             // виконується при створенні: new Task(…)
    this.title = title;
    this.done = false;
  }

  toggle() {                       // метод: функція, що належить обʼєктам класу
    this.done = !this.done;
  }
}

const report = new Task('Звіт');   // екземпляр — обʼєкт, створений за кресленням
const release = new Task('Реліз');
report.toggle();
report.done;                       // true
release.done;                      // false — кожен екземпляр має свої дані

new створює новий порожній обʼєкт і викликає constructor, де цей обʼєкт доступний як this. Методи пишуть один раз у класі, а працюють вони з даними того обʼєкта, на якому їх викликали.

Хто такий this

this — це «обʼєкт, на якому викликали метод»: те, що стоїть ліворуч від крапки в момент виклику. report.toggle() — всередині this це report. release.toggle() — release. Як слово «я» в реченні: воно означає того, хто говорить зараз.

Саме в цьому пастка: this визначається в момент виклику, а не там, де метод написано. Якщо відірвати метод від обʼєкта й викликати окремо, ліворуч від крапки нічого немає — і this стає undefined:

const toggle = report.toggle;      // взяли саму функцію, без обʼєкта
toggle();                          // ✗ TypeError: Cannot read properties of undefined (reading 'done')

Так ніхто не пише навмисно. Але те саме відбувається непомітно, коли метод передають як колбек — у setTimeout, addEventListener, subscribe. Функцію передали, а викличе її вже хтось інший, і this буде тим, що підставить він: undefined, глобальний обʼєкт window чи щось зовсім стороннє. Твого обʼєкта там не буде точно.

Запусти й порівняй два рядки виводу. Другий — від setTimeout: помилки немає, і це гірше за помилку. setTimeout підставив як this глобальний обʼєкт, тож title — undefined, а done мовчки записався не в задачу. Потім заміни останній рядок на setTimeout(() => report.toggle(), 0);

Як не загубити this

Рішення спирається на одну властивість стрілкових функцій: у стрілки немає свого this. Вона бере його з місця, де її створили, — як будь-яку іншу змінну з рюкзака (урок 7). Тому:

// 1. обгорнути виклик у стрілку — найчастіший спосіб
setTimeout(() => report.toggle(), 0);

// 2. метод-стрілка як поле класу: this назавжди привʼязаний до екземпляра
class Task {
  done = false;
  toggle = () => { this.done = !this.done; };
}

// 3. bind — старіший спосіб, трапляється в чужому коді
setTimeout(report.toggle.bind(report), 0);

В Angular у шаблоні (click)="save()" про це думати не треба: Angular сам викликає метод на компоненті. А от коли передаєш метод колбеком — stream.subscribe(this.handle) — ось тут this загубиться. Пиши subscribe(v => this.handle(v)).

Timer виводить тік NaN: метод tick віддали в setTimeout без обʼєкта, і this.ticks рахується не в таймері. Виправ start() так, щоб вивелось тік 1.

Усередині start напиши setTimeout(() => this.tick(), 10); — стрілка візьме this з методу start, а там це сам таймер.

Поля, геттери, приватне

Сучасний клас зазвичай пишуть без constructor — поля оголошують прямо в тілі:

class Cart {
  items = [];                      // поле з початковим значенням
  #discount = 0;                   // приватне поле: ззовні недоступне

  add(price) {
    this.items.push(price);
  }

  get total() {                    // геттер: читається як поле, рахується як функція
    return this.items.reduce((sum, p) => sum + p, 0) - this.#discount;
  }
}

const cart = new Cart();
cart.add(100);
cart.total;                        // 100 — без дужок, бо це геттер

Саме так виглядають компоненти Angular: поля (tasks = signal([])), методи (add(), remove()), геттери. А замість # у TypeScript частіше пишуть private — про це у Ф3.

Допиши клас Cart: поле items (порожній масив), метод add(price) і геттер total — сума цін. Має вивестись 150.

Подивись на приклад над завданням — потрібне майже те саме, без знижки.

Наслідування — коротко

class Bug extends Task {           // Bug — це Task з чимось ще
  constructor(title, severity) {
    super(title);                  // спершу конструктор батька
    this.severity = severity;
  }
}

extends бере все з батьківського класу, super(…) викликає його конструктор. В Angular наслідування трапляється рідко: замість «бути різновидом» компонент отримує потрібне через DI — урок про сервіси в модулі 2.

Як це виглядає в Angular

export class TaskList {                       // компонент — звичайний клас
  tasks = signal<Task[]>([]);                  // поле; <Task[]> — тип з TypeScript, Ф3

  add(title: string) {                         // метод; : string — теж тип
    this.tasks.update(list => [...list, { title, done: false }]);
  }
}

Тепер кожен шматок має бути знайомим: поле класу, метод, this, стрілка-колбек, spread і коротка властивість { title }. Типи TypeScript — останнє, чого бракує, і цьому присвячено Ф3.

Типові помилки
  • Метод передали колбеком — setTimeout(this.save), subscribe(this.handle) — і this загубився. Іноді це помилка, а іноді тиха робота не з тим обʼєктом. Обгортай у стрілку.
  • Забутий new: Task('Звіт') замість new Task('Звіт') — помилка.
  • Геттер викликають з дужками: cart.total() — а це не функція.
  • У методі звертаються до поля без this: items.push(…) замість this.items.push(…).

Підсумок

  • Клас — креслення, new створює екземпляр, constructor його налаштовує.
  • this — обʼєкт ліворуч від крапки в момент виклику. Відірвав метод від обʼєкта — this загубився.
  • У стрілки немає свого this, вона бере його з місця створення — тому колбеки обгортають у стрілки.
  • Компонент Angular — клас з полями, методами й геттерами.