Хранение предыдущих значений

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


Хук useRef для хранения предыдущих значений

В Haunted ключевым инструментом для хранения данных между рендерами является useRef. Он возвращает объект с полем current, которое сохраняет значение между вызовами функции компонента.

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

import { html, component, useRef, useEffect } FROM 'haunted';

function Counter() {
  const count = useRef(0);
  const prevCount = useRef(null);

  useEffect(() => {
    prevCount.current = count.current;
  });

  const increment = () => {
    count.current += 1;
    // Перерендер компонента не происходит автоматически при изменении count.current
  };

  return html`
    <div>
      <p>Текущее значение: ${count.current}</p>
      <p>Предыдущее значение: ${prevCount.current}</p>
      <button @click=${increment}>Увеличить</button>
    </div>
  `;
}

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

Особенности:

  • prevCount.current сохраняет значение, которое было до последнего изменения.
  • В Haunted изменение ref.current не вызывает автоматический ререндер, поэтому для реактивности часто используют useState совместно с useRef.

Сочетание useState и useRef для отслеживания изменений

Для реактивного отслеживания предыдущих значений удобно комбинировать useState и useRef:

import { html, component, useState, useRef, useEffect } FROM 'haunted';

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

  useEffect(() => {
    prevCount.current = count;
  }, [count]);

  const increment = () => setCount(count + 1);

  return html`
    <div>
      <p>Текущее значение: ${count}</p>
      <p>Предыдущее значение: ${prevCount.current}</p>
      <button @click=${increment}>Увеличить</button>
    </div>
  `;
}

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

Пояснения:

  • useEffect с зависимостью [count] выполняется после рендера, что позволяет сохранить предыдущее значение count до следующего изменения.
  • Это позволяет использовать prevCount.current для сравнений, условных эффектов и анимаций.

Использование пользовательского хука usePrevious

Для удобства часто создают отдельный хук usePrevious, который инкапсулирует всю логику хранения предыдущего значения:

import { useRef, useEffect } from 'haunted';

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

Применение в компоненте:

import { html, component, useState } from 'haunted';
import { usePrevious } from './usePrevious.js';

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

  return html`
    <div>
      <p>Текущее значение: ${count}</p>
      <p>Предыдущее значение: ${prevCount}</p>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
    </div>
  `;
}

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

Преимущества подхода:

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

Отслеживание нескольких предыдущих состояний

Иногда требуется хранить не только одно предыдущее значение, но и несколько:

import { useRef, useEffect } from 'haunted';

function useHistory(value, LIMIT = 5) {
  const history = useRef([]);

  useEffect(() => {
    history.current.push(value);
    if (history.current.length > LIMIT) {
      history.current.shift();
    }
  }, [value]);

  return history.current;
}

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

import { html, component, useState } from 'haunted';
import { useHistory } from './useHistory.js';

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

  return html`
    <div>
      <p>История: ${history.join(', ')}</p>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
    </div>
  `;
}

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

Особенности:

  • Хранится ограниченный массив предыдущих значений.
  • Можно использовать для визуализации изменений, undo/redo и анимаций.

Советы по оптимизации

  1. Минимизировать количество сохранённых данных — хранить только необходимое для логики.
  2. Использовать useRef вместо useState для данных, не влияющих на рендер, чтобы избежать лишних обновлений.
  3. Комбинировать с useEffect для выполнения действий после изменения значения.
  4. Создавать кастомные хуки для повторного использования логики и улучшения читаемости кода.

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