Множественные состояния

Haunted — это современная библиотека для создания веб-компонентов с реактивным подходом, основанная на функциональных компонентах и хуках, аналогичных React. Одним из ключевых инструментов для управления состоянием является useState, но при работе со сложными компонентами часто возникает необходимость управлять множественными состояниями одновременно.

Использование нескольких useState

Haunted позволяет создавать любое количество независимых состояний с помощью useState. Каждый вызов useState возвращает пару: текущее значение и функцию для его обновления. Пример:

import { component, useState, html } from 'haunted';

function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <button @click=${() => setCount(count + step)}>Увеличить</button>
      <input type="number" .value=${step} @input=${e => setStep(+e.target.value)} />
    </div>
  `;
}

customElements.define('multi-counter', component(Counter));

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

Объединение состояний в объект

Для группировки связанных состояний часто используют объект состояния. В этом случае useState хранит один объект с несколькими полями:

function Form() {
  const [formData, setFormData] = useState({ name: '', email: '' });

  const handleChange = e => {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  };

  return html`
    <form>
      <input name="name" .value=${formData.name} @input=${handleChange} placeholder="Имя" />
      <input name="email" .value=${formData.email} @input=${handleChange} placeholder="Email" />
      <p>Данные: ${JSON.stringify(formData)}</p>
    </form>
  `;
}

customElements.define('form-component', component(Form));

Такой подход уменьшает количество вызовов useState и позволяет легко управлять комплексными формами. Важно всегда использовать иммутабельное обновление объекта, чтобы Haunted корректно отслеживал изменения.

Множественные состояния и производные значения

Иногда одно состояние зависит от других. В Haunted нет встроенного хука useEffect как в React, но есть useEffect для побочных эффектов. Пример синхронизации производного состояния:

import { component, useState, useEffect, html } from 'haunted';

function ScoreTracker() {
  const [score, setScore] = useState(0);
  const [bonus, setBonus] = useState(0);
  const [total, setTotal] = useState(0);

  useEffect(() => {
    setTotal(score + bonus);
  }, [score, bonus]);

  return html`
    <div>
      <p>Очки: ${score}</p>
      <p>Бонус: ${bonus}</p>
      <p>Итого: ${total}</p>
      <button @click=${() => setScore(score + 1)}>Добавить очко</button>
      <button @click=${() => setBonus(bonus + 2)}>Добавить бонус</button>
    </div>
  `;
}

customElements.define('score-tracker', component(ScoreTracker));

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

Деструктуризация и управление массивами состояний

Для управления множеством однотипных данных часто используют массивы:

function TodoList() {
  const [todos, setTodos] = useState([
    { text: 'Купить хлеб', done: false },
    { text: 'Сделать домашку', done: false },
  ]);

  const toggleTodo = index => {
    setTodos(todos.map((todo, i) => i === index ? { ...todo, done: !todo.done } : todo));
  };

  return html`
    <ul>
      ${todos.map((todo, index) => html`
        <li @click=${() => toggleTodo(index)} style="text-decoration: ${todo.done ? 'line-through' : 'none'}">
          ${todo.text}
        </li>
      `)}
    </ul>
  `;
}

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

Такой подход позволяет реагировать на изменения конкретных элементов массива, сохраняя реактивность всего компонента.

Рекомендации по структурированию множественных состояний

  1. Использовать отдельные useState для независимых переменных.
  2. Объединять связанные состояния в объект или массив, если они логически связаны.
  3. Обновлять состояния иммутабельно, чтобы Haunted корректно триггерил ререндер.
  4. Использовать useEffect для производных данных, чтобы избежать дублирования логики.
  5. Минимизировать количество useState для производительности, особенно при больших компонентах.

Пример комплексного компонента

function Dashboard() {
  const [user, setUser] = useState({ name: '', age: 0 });
  const [stats, setStats] = useState({ points: 0, level: 1 });
  const [notifications, setNotifications] = useState([]);

  useEffect(() => {
    if (stats.points > 100) setStats({ ...stats, level: stats.level + 1 });
  }, [stats.points]);

  return html`
    <div>
      <h2>${user.name || 'Гость'}, уровень ${stats.level}</h2>
      <p>Очки: ${stats.points}</p>
      <button @click=${() => setStats({ ...stats, points: stats.points + 10 })}>Добавить очки</button>
      <ul>
        ${notifications.map(n => html`<li>${n}</li>`)}
      </ul>
    </div>
  `;
}

customElements.define('dashboard-component', component(Dashboard));

В этом компоненте несколько состояний объединены логически: пользовательские данные, статистика и уведомления. useEffect следит за изменением очков и автоматически повышает уровень.

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