Синхронные эффекты

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


useEffect и синхронные эффекты

В Haunted синхронные эффекты создаются с помощью useEffect, передавая в него функцию, которая возвращает функцию очистки (cleanup), если это необходимо. Синхронные эффекты вызываются после того, как компонент обновил DOM, но до завершения следующего цикла браузера. Это позволяет:

  • манипулировать DOM непосредственно после рендера;
  • гарантировать последовательность вызова эффектов;
  • выполнять синхронную инициализацию зависимостей.

Пример базового синхронного эффекта:

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

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

  useEffect(() => {
    console.log('Синхронный эффект: текущий count =', count);
    return () => console.log('Очистка эффекта для count =', count);
  }, [count]);

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

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

Ключевой момент: функция эффекта вызывается после каждого рендера, где изменились зависимости. Если зависимости не указаны, эффект выполняется после каждого рендера компонента.


Отличие синхронных и асинхронных эффектов

В Haunted все эффекты по умолчанию синхронные. Основное отличие заключается в том, что асинхронные действия (например, fetch-запросы, таймеры, события) должны быть обёрнуты внутри эффекта, но сам эффект при этом запускается синхронно после рендера.

Пример асинхронной работы внутри синхронного эффекта:

useEffect(() => {
  let cancelled = false;

  async function fetchData() {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    if (!cancelled) {
      setData(data);
    }
  }

  fetchData();

  return () => {
    cancelled = true; // предотвращение обновления после размонтирования
  };
}, []);

Даже если внутренняя функция асинхронная, сама функция эффекта выполняется синхронно.


Очистка синхронных эффектов

Каждый вызов useEffect может возвращать функцию очистки. Это особенно важно для предотвращения утечек памяти при работе с подписками, таймерами или сторонними библиотеками, которые модифицируют DOM.

Пример использования функции очистки:

useEffect(() => {
  const intervalId = setInterval(() => {
    console.log('Таймер срабатывает каждые 1 секунду');
  }, 1000);

  return () => clearInterval(intervalId);
}, []);

Функция очистки вызывается перед повторным выполнением эффекта и при размонтировании компонента.


Управление зависимостями эффекта

Аргумент dependencies позволяет Haunted оптимизировать вызовы эффектов, выполняя их только при изменении указанных значений.

  • [] — эффект вызовется только один раз при монтировании.
  • [value1, value2] — эффект вызовется при изменении value1 или value2.
  • Отсутствие массива — эффект выполняется после каждого рендера.

Пример:

const [width, setWidth] = useState(window.innerWidth);

useEffect(() => {
  const handleResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', handleResize);

  return () => window.removeEventListener('resize', handleResize);
}, []);

В этом примере синхронный эффект обеспечивает корректное подключение и удаление обработчика события.


Последовательность вызова эффектов

Если компонент содержит несколько useEffect, они вызываются в порядке объявления. Очистка выполняется также в обратном порядке. Это позволяет контролировать порядок инициализации ресурсов и их освобождения.

useEffect(() => {
  console.log('Эффект 1');
  return () => console.log('Очистка эффекта 1');
}, []);

useEffect(() => {
  console.log('Эффект 2');
  return () => console.log('Очистка эффекта 2');
}, []);

Вывод в консоли:

Эффект 1
Эффект 2
Очистка эффекта 2
Очистка эффекта 1

Практические сценарии применения синхронных эффектов

  1. Инициализация сторонних библиотек — подключение визуальных компонентов, слайдеров, графиков.
  2. Манипуляции с DOM — установка фокуса, прокрутка, измерения размеров элементов.
  3. Подписки на события — мышь, клавиатура, события окна.
  4. Синхронизация состояния с внешними данными — localStorage, cookies, глобальные объекты.

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


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

  • Минимизировать количество вычислений внутри эффекта; тяжёлые операции лучше выполнять асинхронно.
  • Всегда возвращать функцию очистки при работе с подписками или таймерами.
  • Явно указывать зависимости, чтобы избежать лишних вызовов и непредсказуемого поведения.
  • Разделять эффекты по назначению — один эффект на один тип побочного действия.

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