Мутабельные значения

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


Понятие мутабельных значений

Мутабельные значения (mutable values) — это объекты, содержащие внутреннее состояние, которое может изменяться без необходимости полного пересоздания компонента. В Haunted они реализуются через функцию useMutable, которая предоставляет ссылку на текущее значение и механизм для его обновления.

Сигнатура useMutable:

const state = useMutable(initialValue);
  • initialValue — начальное значение, которое может быть любого типа: объект, массив, число, строка.
  • state — объект с единственным свойством current, через которое производится доступ и изменение значения.

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

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

function Counter() {
  const counter = useMutable(0);

  const increment = () => {
    counter.current += 1;
  };

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

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

В этом примере:

  • counter — мутабельное значение.
  • Изменение counter.current автоматически триггерит повторный рендер компонента.
  • Нет необходимости вызывать дополнительные функции для обновления состояния, как в классическом React (setState).

Отличие от useState

В Haunted существует также useState, однако ключевое отличие заключается в том, что useState возвращает массив [value, setValue], а useMutable — объект с .current.

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

  1. Позволяет изменять сложные объекты без полного копирования.
  2. Упрощает работу с вложенными структурами.
  3. Минимизирует создание новых объектов при каждом обновлении.

Недостатки:

  • Требуется осторожность при прямом изменении вложенных объектов, чтобы избежать непреднамеренного поведения при рендеринге.
  • Не всегда подходит для случаев, где важно иммутабельное обновление состояния.

Работа с объектами и массивами

Мутабельные значения особенно удобны для массивов и объектов, когда нужно изменять отдельные элементы без пересоздания всего объекта.

Пример с массивом:

function TodoList() {
  const todos = useMutable([]);

  const addTodo = (text) => {
    todos.current.push({ text, done: false });
  };

  const toggleTodo = (index) => {
    todos.current[index].done = !todos.current[index].done;
  };

  return html`
    <ul>
      ${todos.current.map((todo, i) => html`
        <li @click=${() => toggleTodo(i)} style="text-decoration: ${todo.done ? 'line-through' : 'none'};">
          ${todo.text}
        </li>
      `)}
    </ul>
    <button @click=${() => addTodo('Новая задача')}>Добавить</button>
  `;
}
  • Каждое изменение todos.current автоматически обновляет DOM.
  • Не нужно создавать новый массив при каждом добавлении или изменении элемента.
  • Вложенные изменения работают напрямую, если они не требуют глубокого клонирования.

Использование в сложных компонентах

Для больших компонентов с множеством взаимосвязанных состояний useMutable позволяет:

  • Хранить состояния нескольких частей компонента в отдельных мутабельных объектах.
  • Минимизировать ререндеринг неизменных частей.
  • Удобно передавать мутабельные объекты в дочерние компоненты без потери реактивности.

Пример:

function Dashboard() {
  const user = useMutable({ name: 'Алексей', age: 28 });
  const notifications = useMutable([]);

  const addNotification = (msg) => notifications.current.push(msg);
  const updateName = (newName) => user.current.name = newName;

  return html`
    <div>
      <h2>Пользователь: ${user.current.name}</h2>
      <p>Возраст: ${user.current.age}</p>
      <ul>
        ${notifications.current.map(n => html`<li>${n}</li>`)}
      </ul>
    </div>
  `;
}
  • Разделение состояний по смыслу делает код более читаемым.
  • Изменение user.current или notifications.current не влияет на другие части компонента, пока они не используются в шаблоне.

Интеграция с эффектами

Мутабельные значения хорошо работают совместно с useEffect для отслеживания изменений и выполнения побочных эффектов:

import { useEffect } from 'haunted';

function Timer() {
  const time = useMutable(0);

  useEffect(() => {
    const id = setInterval(() => time.current += 1, 1000);
    return () => clearInterval(id);
  }, []);

  return html`<p>Время: ${time.current} сек</p>`;
}
  • Изменение time.current автоматически обновляет отображение.
  • useEffect корректно очищает таймер при размонтировании компонента.

Рекомендации по использованию

  • Для примитивных значений (number, string) useMutable удобен для упрощённого синтаксиса, но можно использовать useState для стандартного подхода.
  • Для объектов и массивов useMutable позволяет изменять вложенные элементы без копирования.
  • Изменения следует производить через .current — это гарантирует реактивность.
  • Вложенные объекты можно комбинировать с дополнительными мутабельными значениями для отдельных веток состояния.

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