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 не вызывает повторный
рендер.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`
`;
}
Особенности:
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)} />`;
}
Преимущества:
delay.Утилитарные хуки можно комбинировать между собой для построения
сложной логики. Например, можно использовать
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
` : ''}
`;
}
Выводы из практики: