Observer паттерн и реактивность

Lit — это современный веб-фреймворк для создания легковесных компонентов с декларативным рендерингом. Ключевым аспектом его работы является реактивность, которая позволяет автоматически обновлять DOM при изменении состояния компонента. Основой этой реактивности в Lit служит наблюдаемый (observable) объект состояния и паттерн Observer.

Реактивные свойства

В Lit реактивные свойства определяются с помощью декоратора @property или через статическое поле properties. Эти свойства автоматически отслеживают изменения и вызывают повторный рендеринг компонента.

Пример:

import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';

class MyCounter extends LitElement {
  @property({ type: Number })
  count = 0;

  render() {
    return html`
      <div>Счётчик: ${this.count}</div>
      <button @click=${() => this.count++}>Увеличить</button>
    `;
  }
}

customElements.define('my-counter', MyCounter);

В этом примере свойство count является реактивным. Любое его изменение вызывает обновление шаблона. Это и есть применение Observer-паттерна: компонент подписан на изменения своих свойств, и DOM реагирует на эти изменения автоматически.

Подписка на изменения свойств

Lit позволяет использовать updated(changedProperties) для реакции на изменения свойств. Метод вызывается после каждого обновления компонента.

updated(changedProperties) {
  if (changedProperties.has('count')) {
    console.log(`Счётчик изменён: ${this.count}`);
  }
}

Метод updated обеспечивает контроль над реактивностью, позволяя выполнять побочные эффекты при изменении конкретных свойств. Это прямое использование наблюдателя: компонент наблюдает за изменением данных и реагирует на них.

Observer-паттерн в глубине Lit

Внутри Lit реактивные свойства реализуются через getter/setter. Когда свойство изменяется, срабатывает внутренний механизм, который ставит компонент в очередь на обновление. Это полностью соответствует принципам Observer:

  • Субъект (Subject) — реактивное свойство.
  • Наблюдатели (Observers) — компоненты и шаблон, которые зависят от свойства.
  • Уведомление (Notification) — автоматический вызов рендера при изменении свойства.

Реактивные коллекции

Lit также поддерживает реактивные массивы и объекты через ReactiveController или использование библиотек для реактивных структур (например, @lit/reactive-element). Для массивов и объектов важно создавать новые экземпляры при изменении, чтобы наблюдатель корректно зафиксировал обновление.

Пример с массивом:

import { LitElement, html } from 'lit';
import { property } from 'lit/decorators.js';

class TodoList extends LitElement {
  @property({ type: Array })
  todos = [];

  addTodo(todo) {
    this.todos = [...this.todos, todo]; // создаем новый массив для триггера реакции
  }

  render() {
    return html`
      <ul>
        ${this.todos.map(todo => html`<li>${todo}</li>`)}
      </ul>
      <button @click=${() => this.addTodo('Новая задача')}>Добавить</button>
    `;
  }
}

customElements.define('todo-list', TodoList);

Создание нового массива является обязательным, потому что прямое изменение существующего (push) не вызывает реактивного обновления. Это связано с тем, что Observer следит за ссылкой на объект, а не за его внутренними мутациями.

Контроллеры для сложной реактивности

Для более сложных сценариев используется ReactiveController, который позволяет создавать отдельные логические блоки с реактивными зависимостями.

Пример:

import { ReactiveController, ReactiveControllerHost } from 'lit';

class LoggerController implements ReactiveController {
  host;
  constructor(host: ReactiveControllerHost) {
    this.host = host;
    host.addController(this);
  }

  hostUpdated() {
    console.log('Компонент обновился', this.host);
  }
}

Контроллер подписывается на жизненный цикл компонента и получает уведомления об обновлениях, что делает его расширением Observer-паттерна на уровне архитектуры компонента.

Декларативный рендеринг и реактивность

Главная ценность реактивности в Lit заключается в декларативном рендеринге. Вместо прямого манипулирования DOM с помощью querySelector и innerHTML, изменения состояния автоматически отражаются в интерфейсе. Это снижает сложность кода и исключает необходимость вручную отслеживать обновления.

Ключевые моменты:

  • Каждое реактивное свойство — наблюдаемый объект.
  • Любое изменение свойства уведомляет подписчиков (шаблон, контроллеры).
  • Рендер выполняется автоматически после изменения состояния.
  • Observer-паттерн реализован через встроенные getter/setter и жизненный цикл компонента.

Практические советы

  • Всегда использовать декоратор @property для реактивных свойств.
  • Для сложных объектов и массивов создавать новые экземпляры при изменениях.
  • Для побочных эффектов использовать updated(changedProperties).
  • ReactiveController помогает вынести реактивную логику за пределы основного компонента.

Reactivity в Lit позволяет строить чистые, модульные и высокопроизводительные веб-компоненты, полностью опираясь на принципы Observer и декларативного рендеринга.