Минимизация перерисовок

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


Реактивные свойства и их влияние на перерисовку

В Slim.js реактивность строится на свойствах, объявленных через @state или @property. Любое изменение таких свойств автоматически инициирует обновление компонента:

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

class Counter extends Slim {
  @state count = 0;

  increment() {
    this.count++;
  }

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

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

Особенности работы:

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

Разделение состояния

Для уменьшения количества перерисовок рекомендуется делить состояние на мелкие, независимые свойства, вместо хранения всех данных в одном объекте:

@state firstName = '';
@state lastName = '';
@state age = 0;

Если объединить все данные в один объект:

@state user = { firstName: '', lastName: '', age: 0 };

Любое изменение одного поля (user.firstName = 'Иван') приведёт к полной перерисовке всего блока, где используется user. Разделение свойств делает обновления локальными.


Использование вычисляемых свойств

Slim.js поддерживает геттеры, которые могут выступать в роли вычисляемых свойств. Такие свойства не хранят состояние напрямую, но реагируют на изменения зависимых @state полей:

get fullName() {
  return `${this.firstName} ${this.lastName}`;
}

В шаблоне fullName обновляется автоматически при изменении firstName или lastName, при этом Slim.js минимизирует изменение только текстового узла, а не всего родительского элемента.


Оптимизация списков

При рендеринге списков важно использовать key для каждого элемента, чтобы Slim.js мог правильно дифференцировать элементы и избегать полной перерисовки списка:

<ul>
  ${this.items.map(item => html`<li key=${item.id}>${item.name}</li>`)}
</ul>

Без key Slim.js может пересоздать все элементы списка при каждом обновлении, даже если изменился только один элемент. С key изменяется только конкретный элемент, что значительно снижает нагрузку на DOM.


Работа с событиями

События, привязанные через синтаксис @event, автоматически управляются Slim.js. Для минимизации лишних перерисовок:

  • Использовать стрелочные функции с конкретным вызовом метода компонента.
  • Избегать изменения состояния, не влияющего на отображение. Любое изменение @state инициирует реактивное обновление.

Пример:

<button @click=${() => this.toggleActive()}>Переключить</button>

Изменение локального состояния внутри метода toggleActive() обновит только необходимые узлы.


Локальные обновления через requestUpdate()

Иногда требуется принудительное обновление части шаблона без изменения всех реактивных свойств. Для этого можно использовать метод requestUpdate() с указанием конкретного свойства:

this.requestUpdate('count');

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


Избегание ненужных вычислений

  • Выносить тяжелые вычисления из шаблона в отдельные методы.
  • Кешировать результаты, если они зависят от неизменяемых данных.
  • Использовать мемоизацию для сложных фильтров и сортировок списков.

Пример мемоизации:

get filteredItems() {
  if (!this._cached || this._lastFilter !== this.filter) {
    this._cached = this.items.filter(item => item.name.includes(this.filter));
    this._lastFilter = this.filter;
  }
  return this._cached;
}

Работа с вложенными компонентами

Slim.js автоматически обновляет только те компоненты, чьи свойства изменились. Для оптимизации:

  • Передавать в дочерние компоненты только необходимые данные через @property.
  • Избегать передачи объектов или массивов, которые изменяются целиком, если достаточно передачи конкретного значения.
<child-component .value=${this.someValue}></child-component>

Использование ссылочного типа объекта без изменений приведёт к ненужной перерисовке. Лучше передавать примитив или отдельное поле объекта.


Итоговые принципы минимизации перерисовок

  1. Разделять состояние на небольшие реактивные свойства.
  2. Использовать вычисляемые свойства и геттеры для зависимых данных.
  3. Применять key при работе со списками.
  4. Минимизировать изменение объектов и массивов целиком.
  5. Кешировать результаты сложных вычислений.
  6. Использовать requestUpdate() для точечных обновлений.

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