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

Slim.js — это легковесная библиотека для создания веб-компонентов с реактивными возможностями. Одним из ключевых элементов её реактивной архитектуры является подписка на изменения свойств компонентов. Это позволяет автоматически отслеживать изменения состояния и обновлять DOM без ручного вмешательства.


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

В Slim.js свойства компонента, помеченные как реактивные, автоматически отслеживаются системой. Для этого используется декоратор @state или ключевое свойство observed в компоненте. Любое изменение этих свойств триггерит уведомление для подписчиков.

Пример декларации реактивного свойства:

import { Slim, html, state } from 'slim-js';

class Counter extends Slim {
  @state count = 0;

  increment() {
    this.count += 1;
  }

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

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

В этом примере count — реактивное свойство. Любое его изменение автоматически вызовет перерендер компонента.


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

Slim.js позволяет подписываться на изменения конкретных свойств через метод onChange. Это полезно, когда требуется выполнить дополнительные действия при обновлении состояния, например, синхронизировать данные с сервером или изменять другие свойства компонента.

Синтаксис использования:

this.onChange('propertyName', (oldValue, newValue) => {
  console.log(`Свойство изменилось с ${oldValue} на ${newValue}`);
});

Метод принимает два аргумента:

  • propertyName — имя свойства для отслеживания;
  • callback-функция, которая получает старое и новое значения свойства.

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

class UserProfile extends Slim {
  @state username = '';

  constructor() {
    super();
    this.onChange('username', (oldVal, newVal) => {
      console.log(`Имя пользователя изменилось: ${oldVal} → ${newVal}`);
      this.validateUsername(newVal);
    });
  }

  validateUsername(value) {
    this.isValid = value.length >= 3;
  }

  render() {
    return html`
      <input type="text" .value=${this.username} @input=${e => this.username = e.target.value} />
      <p>Статус: ${this.isValid ? 'валидный' : 'слишком короткий'}</p>
    `;
  }
}
customElements.define('user-profile', UserProfile);

В этом примере каждое изменение свойства username автоматически вызывает проверку валидности имени.


Массовая подписка на несколько свойств

Slim.js поддерживает подписку на несколько свойств одновременно. Это достигается передачей массива имен свойств в onChange.

this.onChange(['propA', 'propB'], (oldValue, newValue, propertyName) => {
  console.log(`${propertyName} изменилось: ${oldValue} → ${newValue}`);
});

Такой подход упрощает обработку взаимосвязанных данных и позволяет минимизировать дублирование кода.


Отписка от изменений

Для освобождения ресурсов и предотвращения утечек памяти Slim.js предоставляет возможность отписки. Метод offChange используется для удаления подписчика:

const callback = (oldVal, newVal) => { console.log(newVal); };
this.onChange('count', callback);

// Позже
this.offChange('count', callback);

Если отписка не выполнена, подписчики продолжают получать уведомления, что может привести к неожиданным побочным эффектам.


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

Иногда нужно реагировать на любое изменение любого реактивного свойства. Для этого используется метод observeAll:

this.observeAll((propertyName, oldVal, newVal) => {
  console.log(`Свойство ${propertyName} изменилось: ${oldVal} → ${newVal}`);
});

Этот метод полезен при отладке или при создании компонентов с динамическими связями между свойствами.


Важные нюансы

  1. Старые и новые значения: Slim.js передает корректные старое и новое значение, даже если обновление происходит через объектную мутацию.
  2. Реактивные объекты и массивы: Для глубокого отслеживания изменений объектов или массивов необходимо использовать методы, возвращающие новые ссылки, так как Slim.js отслеживает изменения по ссылке.
  3. Производительность: Частые подписки на большое количество свойств могут снижать производительность. Рекомендуется подписываться только на необходимые свойства.
  4. Комбинированные вычисляемые свойства: Для свойств, зависящих от нескольких других, рекомендуется использовать реактивные вычисляемые свойства (@computed) вместе с onChange для оптимизации обновлений.

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

class ShoppingCart extends Slim {
  @state items = [];
  @state total = 0;

  constructor() {
    super();
    this.onChange('items', () => this.calculateTotal());
  }

  addItem(item) {
    this.items = [...this.items, item]; // важно создавать новый массив для реактивности
  }

  calculateTotal() {
    this.total = this.items.reduce((sum, item) => sum + item.price, 0);
  }

  render() {
    return html`
      <ul>
        ${this.items.map(item => html`<li>${item.name}: $${item.price}</li>`)}
      </ul>
      <p>Итого: $${this.total}</p>
      <button @click=${() => this.addItem({ name: 'Apple', price: 1 })}>Добавить яблоко</button>
    `;
  }
}

customElements.define('shopping-cart', ShoppingCart);

В этом компоненте изменения массива items автоматически запускают перерасчет total, обеспечивая согласованность данных без ручного вызова обновления DOM.


Подписка на изменения в Slim.js является ядром реактивного программирования в библиотеке, обеспечивая автоматическое обновление интерфейса, синхронизацию данных и гибкую архитектуру компонентов. Она позволяет строить сложные динамические приложения с минимальными усилиями по управлению состоянием и DOM.