Паттерны управления состоянием

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

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

В Lit свойства объявляются с использованием декоратора @property или через статическое свойство properties класса компонента:

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

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

  render() {
    return html`
      <div>Счетчик: ${this.count}</div>
      <button @click=${() => this.count++}>Увеличить</button>
    `;
  }
}
customElements.define('counter-component', CounterComponent);

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

  • type задает тип свойства и влияет на десериализацию атрибутов.
  • Изменение свойства count вызывает повторный вызов render().
  • Свойства могут быть связаны с атрибутами HTML, обеспечивая синхронизацию данных.

Локальное и глобальное состояние

В Lit различают два уровня состояния: локальное (внутри компонента) и глобальное (доступное нескольким компонентам).

Локальное состояние управляется реактивными свойствами компонента. Оно изолировано и не влияет на внешние компоненты напрямую. Использование реактивных свойств для локального состояния обеспечивает чистый и предсказуемый рендеринг.

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

import { reactive } from 'lit';

export const store = reactive({
  theme: 'light',
  toggleTheme() {
    this.theme = this.theme === 'light' ? 'dark' : 'light';
  }
});

Компонент может подписываться на изменения:

class ThemeToggler extends LitElement {
  render() {
    return html`
      <button @click=${() => store.toggleTheme()}>
        Тема: ${store.theme}
      </button>
    `;
  }
}
customElements.define('theme-toggler', ThemeToggler);

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

  • reactive() позволяет создавать объекты, изменения которых отслеживаются Lit.
  • Подписка на глобальное состояние упрощает синхронизацию нескольких компонентов.

Декомпозиция состояния

Эффективная организация состояния требует разбиения его на отдельные сегменты:

  • Примитивные свойства: строки, числа, булевы значения, удобно хранить в локальном состоянии.
  • Сложные объекты: массивы, словари, могут использоваться как глобальные хранилища или управляться через методы, инициирующие ререндеринг.
  • Производные состояния: значения, вычисляемые на основе других свойств, лучше хранить как геттеры для уменьшения избыточного состояния.
class TodoList extends LitElement {
  @property({ type: Array }) todos = [];

  get remaining() {
    return this.todos.filter(todo => !todo.completed).length;
  }

  render() {
    return html`
      <div>Осталось дел: ${this.remaining}</div>
    `;
  }
}

Обновление состояния и производительность

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

  • Использование shouldUpdate(changedProperties) позволяет контролировать, когда компонент должен обновляться.
  • Методы, изменяющие состояние, рекомендуется делать атомарными, чтобы избегать промежуточных рендеров.
class OptimizedComponent extends LitElement {
  @property({ type: Number }) value = 0;

  shouldUpdate(changedProperties) {
    return changedProperties.has('value') && this.value % 2 === 0;
  }

  render() {
    return html`<div>Value: ${this.value}</div>`;
  }
}

Паттерн подписки на события и хранилища

Для синхронизации нескольких компонентов используются события и подписки на хранилища:

class GlobalCounter extends LitElement {
  connectedCallback() {
    super.connectedCallback();
    window.addEventListener('increment', this._onIncrement);
  }

  disconnectedCallback() {
    window.removeEventListener('increment', this._onIncrement);
    super.disconnectedCallback();
  }

  _onIncrement = () => {
    this.count++;
  };

  render() {
    return html`<div>Глобальный счетчик: ${this.count}</div>`;
  }
}

Преимущества:

  • Разделение компонентов и состояния.
  • Универсальная реакция на события без прямой зависимости.

Рекомендации по управлению состоянием

  1. Локальное состояние хранить в реактивных свойствах компонента.
  2. Глобальное состояние выносить в отдельные хранилища с реактивными объектами.
  3. Производные значения оформлять через геттеры.
  4. Контролировать ререндеринг через shouldUpdate.
  5. Использовать события для коммуникации между компонентами, избегая прямых ссылок на чужие состояния.

Эти подходы обеспечивают предсказуемую архитектуру, оптимальную производительность и гибкость при масштабировании приложений на Lit.