Утилитарные хуки

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

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


useRef и работа с DOM

Хук useRef позволяет создать ссылку на DOM-элемент или произвольное значение, которое сохраняется между рендерами.

import { html, useRef } from 'haunted';

function InputFocus() {
  const inputRef = useRef();

  const focusInput = () => {
    inputRef.current.focus();
  };

  return html`
    
    
  `;
}

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

  • ref хранит ссылку на элемент или любое значение.
  • Изменение ref.current не вызывает повторный рендер.
  • Идеально подходит для прямого взаимодействия с DOM.

useEventListener для подписки на события

Haunted не имеет встроенного хука с этим названием, но утилитарный подход позволяет создавать такой хук для безопасной подписки на события, с автоматическим удалением при размонтировании:

import { useEffect } from 'haunted';

function useEventListener(target, type, callback) {
  useEffect(() => {
    if (!target) return;
    target.addEventListener(type, callback);
    return () => target.removeEventListener(type, callback);
  }, [target, type, callback]);
}

Применение:

function WindowResizeLogger() {
  useEventListener(window, 'resize', () => console.log(window.innerWidth));

  return html`

Открой консоль и измени размер окна

`; }

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

  • Нет утечек памяти: обработчик автоматически удаляется при размонтировании.
  • Легко интегрировать с любыми элементами или объектами с событиями.

useInterval и управление таймерами

Создание кастомного хука для таймеров упрощает логику повторяющихся действий:

import { useEffect, useRef } from 'haunted';

function useInterval(callback, delay) {
  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay === null) return;
    const id = setInterval(() => savedCallback.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}

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

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

  useInterval(() => setCount(count + 1), 1000);

  return html`

Счётчик: ${count}

`; }

Ключевые моменты:

  • Используется useRef для сохранения актуальной версии колбэка.
  • Позволяет легко стартовать и останавливать интервал через delay.
  • Избегает повторной установки таймера при каждом рендере.

usePrevious для хранения предыдущего состояния

Иногда требуется сравнить текущее значение с предыдущим. Утилитарный хук usePrevious решает эту задачу:

import { useRef, useEffect } from 'haunted';

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

Пример:

function ValueTracker({ value }) {
  const prev = usePrevious(value);

  return html`

Текущее: ${value}, Предыдущее: ${prev}

`; }

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

  • Простое хранение предыдущего состояния.
  • Не требует сложной логики с useState.
  • Совместим со всеми типами данных.

useToggle для булевых состояний

Часто требуется управлять логикой включения/выключения:

import { useState } from 'haunted';

function useToggle(initial = false) {
  const [state, setState] = useState(initial);
  const toggle = () => setState(s => !s);
  return [state, toggle];
}

Применение:

function LightSwitch() {
  const [isOn, toggle] = useToggle(false);

  return html`
    
  `;
}

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

  • Инкапсулирует часто повторяющийся паттерн.
  • Поддерживает начальное состояние.
  • Позволяет легко интегрировать с UI.

useDebounce для оптимизации ввода

Хук useDebounce уменьшает количество срабатываний при вводе или изменении данных:

import { useState, useEffect } from 'haunted';

function useDebounce(value, delay = 300) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    const handler = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(handler);
  }, [value, delay]);

  return debouncedValue;
}

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

function SearchInput() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);

  useEffect(() => {
    console.log('Запрос к API:', debouncedQuery);
  }, [debouncedQuery]);

  return html` setQuery(e.target.value)} />`;
}

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

  • Снижает нагрузку на обработчики ввода и API-запросы.
  • Легко настраивается через параметр delay.
  • Полностью совместим с Haunted-компонентами.

Взаимодействие утилитарных хуков

Утилитарные хуки можно комбинировать между собой для построения сложной логики. Например, можно использовать useEventListener вместе с useRef для отслеживания кликов вне элемента, или useInterval с usePrevious для анимации состояния.

Пример: закрытие выпадающего меню при клике вне элемента:

function Dropdown() {
  const [open, setOpen] = useState(false);
  const ref = useRef();

  useEventListener(document, 'click', e => {
    if (ref.current && !ref.current.contains(e.target)) setOpen(false);
  });

  return html`
    
${open ? html`
  • Пункт 1
  • Пункт 2
` : ''}
`; }

Выводы из практики:

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