Механизм реактивных свойств

Lit — это легковесный JavaScript-фреймворк для создания веб-компонентов, основной упор в котором сделан на производительность и реактивность. Ключевым элементом его работы является система реактивных свойств, позволяющая автоматически отслеживать изменения данных и обновлять DOM без лишних операций.


Определение реактивных свойств

Реактивные свойства в Lit объявляются с помощью статического свойства properties или декоратора @property (при использовании TypeScript или поддерживаемых инструментов). Каждое свойство описывает:

  • Тип данных (type) — String, Number, Boolean, Array, Object.
  • Отражение в атрибуты HTML (reflect) — синхронизация свойства с атрибутом DOM.
  • Производное обновление (hasChanged) — функция для кастомной проверки изменения значения.

Пример объявления через properties:

import { LitElement, html } from 'lit';

class MyComponent extends LitElement {
  static properties = {
    name: { type: String },
    count: { type: Number, reflect: true },
    active: { type: Boolean, hasChanged: (value, oldValue) => value !== oldValue }
  };

  constructor() {
    super();
    this.name = 'Lit';
    this.count = 0;
    this.active = false;
  }

  render() {
    return html`
      <p>${this.name} — ${this.count}</p>
      <p>Active: ${this.active}</p>
    `;
  }
}

customElements.define('my-component', MyComponent);

В этом примере изменения count автоматически отражаются на атрибуте DOM, а active проверяет изменение через функцию hasChanged.


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

Основная логика работы реактивных свойств в Lit строится вокруг наблюдателей и очередей обновления:

  1. Когда свойство изменяется, Lit проверяет, действительно ли значение изменилось, используя hasChanged.
  2. Если значение изменилось, компонент помещается в очередь обновлений.
  3. В следующем цикле микротасков происходит вызов метода update, который вызывает render.
  4. После рендера вызывается updated, позволяющий выполнить побочные эффекты после обновления DOM.

Это обеспечивает минимальное количество перерисовок и предотвращает лишние обновления при множественных изменениях свойств в одном цикле.


Синхронизация с атрибутами

Если свойство объявлено с reflect: true, Lit автоматически синхронизирует его значение с HTML-атрибутом. Это позволяет:

  • Поддерживать совместимость с обычными DOM-атрибутами.
  • Легко изменять состояние компонента через HTML.

Пример:

<my-component count="5"></my-component>

При загрузке страницы значение атрибута count будет преобразовано в тип Number и установлено в свойство count компонента.


Кастомные проверки изменения (hasChanged)

Функция hasChanged(newValue, oldValue) позволяет задать точный критерий изменения. По умолчанию Lit использует проверку !==. Однако для сложных объектов или массивов может потребоваться глубокое сравнение:

activeItems: {
  type: Array,
  hasChanged(newVal, oldVal) {
    return JSON.stringify(newVal) !== JSON.stringify(oldVal);
  }
}

Это особенно полезно, когда требуется реактивность на структурные изменения массивов и объектов.


Локальные и вычисляемые свойства

Reactivity Lit поддерживает вычисляемые свойства, которые могут зависеть от других реактивных свойств. Реализация чаще всего делается через геттеры:

get status() {
  return this.active ? 'Active' : 'Inactive';
}

Такое свойство не является реактивным самостоятельно, но при изменении зависимого свойства active его значение автоматически обновляется в шаблоне.


Методы жизненного цикла, связанные с реактивностью

  • willUpdate(changedProperties) — вызывается перед обновлением, с информацией о свойствах, которые изменились.
  • update(changedProperties) — основной метод обновления, можно переопределять для кастомной логики.
  • updated(changedProperties) — вызывается после завершения рендера, позволяет выполнять DOM-операции.

Пример использования:

updated(changedProperties) {
  if (changedProperties.has('count')) {
    console.log(`Count изменился: ${this.count}`);
  }
}

Рекомендации по использованию реактивных свойств

  • Типизация — всегда указывать type, чтобы Lit корректно преобразовывал значения из атрибутов.
  • Reflect — использовать для свойств, которые должны быть доступны в HTML.
  • Избегать глубоких объектов без hasChanged — иначе компонент не будет отслеживать изменения.
  • Минимизировать сложные вычисления в render — лучше использовать вычисляемые свойства.

Система реактивных свойств делает Lit мощным инструментом для построения динамичных компонентов с минимальными накладными расходами и высокой производительностью.