Состояние-объект и состояние-примитив

Haunted — это библиотека для создания веб-компонентов с использованием реактивного подхода, вдохновлённого React, но построенного на стандарте Web Components. Важнейшей частью построения компонентов в Haunted является управление состоянием. Состояние может быть объектом или примитивом, и понимание различий между ними критично для правильного обновления интерфейса.


Состояние-примитив

Примитивные значения в JavaScript включают string, number, boolean, null и undefined. В Haunted их использование очень похоже на работу с обычными реактивными переменными в других фреймворках.

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

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

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

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

Особенности состояния-примитива:

  1. Иммутабельность: Примитивы заменяются целиком. Каждое обновление создаёт новое значение.
  2. Простота обновления: setCount(newValue) сразу триггерит ререндер компонента.
  3. Реактивность: Haunted автоматически отслеживает изменения через useState и обновляет DOM.

Ключевой момент — нельзя мутировать примитив напрямую. Например, count++ не вызовет перерисовку, если не использовать setCount.


Состояние-объект

С объектами ситуация более сложная. В JavaScript объекты и массивы передаются по ссылке, поэтому простое изменение поля объекта не всегда приводит к обновлению компонента.

function UserProfile() {
  const [user, setUser] = useState({ name: 'Алексей', age: 30 });

  const incrementAge = () => {
    setUser({ ...user, age: user.age + 1 });
  };

  return html`
    <div>
      <p>Имя: ${user.name}</p>
      <p>Возраст: ${user.age}</p>
      <button @click=${incrementAge}>Добавить год</button>
    </div>
  `;
}

customElements.define('user-profile', component(UserProfile));

Особенности состояния-объекта:

  1. Необходимость иммутабельности: Прямое изменение свойства user.age++ не вызовет ререндер. Требуется создавать новый объект с обновлёнными полями ({ ...user, age: user.age + 1 }).
  2. Глубокая реактивность: Haunted не отслеживает вложенные изменения автоматически. Если объект содержит вложенные объекты, их также нужно заменять полностью, чтобы вызвать обновление.
  3. Удобство группировки данных: Объекты позволяют хранить несколько связанных значений в одном состоянии, что упрощает организацию данных внутри компонента.

Использование массивов как состояния

Массивы в Haunted работают аналогично объектам. Любые изменения, которые не создают новый массив, не вызовут ререндер:

function TodoList() {
  const [todos, setTodos] = useState(['Купить молоко', 'Прочитать книгу']);

  const addTodo = () => {
    setTodos([...todos, 'Новая задача']);
  };

  return html`
    <ul>
      ${todos.map(todo => html`<li>${todo}</li>`)}
    </ul>
    <button @click=${addTodo}>Добавить задачу</button>
  `;
}

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

Принципы работы с массивами:

  • Мутирование через методы вроде push, pop, splice не триггерит ререндер.
  • Создание нового массива через спред-оператор или slice гарантирует обновление интерфейса.
  • Можно комбинировать массивы с объектами для сложного состояния, но важно всегда заменять верхний уровень объекта или массива.

Сравнение состояния-примитива и состояния-объекта

Характеристика Примитив Объект / Массив
Обновление состояния Простое (setValue) Только через новый объект/массив
Автоматическая реактивность Полная Только верхний уровень
Хранение нескольких значений Неподходяще Подходит для комплексных структур
Мутирование Невозможно без set Прямое изменение не срабатывает

Лучшие практики работы с состоянием

  1. Использовать примитивы для единичных значений, таких как счётчики, флаги или текстовые поля.
  2. Использовать объекты для связанных данных, например, формы, профили пользователей, списки задач.
  3. Всегда создавать новый объект/массив при изменении состояния, чтобы гарантировать перерисовку компонента.
  4. Минимизировать глубину вложенности состояния, чтобы не усложнять обновление. Если вложенность высокая, стоит рассмотреть отдельные состояния для каждого уровня.
  5. Функции обновления состояния должны быть чистыми и возвращать новый объект/массив, избегая побочных эффектов.

Хуки useState и обновление состояния

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

  • Любое обновление через setState запускает повторный рендер текущего компонента.
  • Обновление состояния не происходит мгновенно, но гарантирует консистентность интерфейса.
  • Можно передавать функцию в setState, чтобы получить актуальное значение, что особенно полезно при работе с объектами или массивами:
setUser(prev => ({ ...prev, age: prev.age + 1 }));

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


Состояние-примитив и состояние-объект в Haunted создают фундамент для реактивного поведения веб-компонентов. Понимание их особенностей позволяет строить интерфейсы, которые правильно реагируют на изменения данных без лишнего кода и сложных обходных решений.