Haunted — это легковесная библиотека для создания веб-компонентов с реактивным подходом, вдохновлённая React Hooks. Одной из ключевых особенностей является поддержка асинхронных операций и обновлений состояния без блокировки интерфейса, что делает работу с сетевыми запросами, таймерами и другими асинхронными процессами удобной и безопасной.
useState и useEffectВ Haunted для хранения состояния используется хук
useState. Асинхронные операции часто инициируются внутри
useEffect, который позволяет реагировать на изменения
зависимостей или выполнять код при монтировании компонента.
Пример асинхронной загрузки данных:
import { html, component, useState, useEffect } from 'haunted';
function AsyncDataComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let canceled = false;
async function fetchData() {
try {
const response = await fetch('https://api.example.com/items');
const result = await response.json();
if (!canceled) {
setData(result);
}
} catch (error) {
console.error('Ошибка загрузки данных', error);
} finally {
if (!canceled) setLoading(false);
}
}
fetchData();
return () => {
canceled = true;
};
}, []);
return html`
${loading ? html`<p>Загрузка...</p>` : html`<pre>${JSON.stringify(data, null, 2)}</pre>`}
`;
}
customElements.define('async-data-component', component(AsyncDataComponent));
Ключевые моменты:
useEffect можно использовать асинхронные
функции, но сам useEffect не может быть
async.canceled предотвращает попытку обновления
состояния после размонтирования компонента, что исключает ошибки типа
“Cannot update state of unmounted component”.Haunted позволяет обрабатывать асинхронные события без блокировки UI. Например, отправка формы:
function FormComponent() {
const [status, setStatus] = useState('');
async function handleSubmit(event) {
event.preventDefault();
setStatus('Отправка...');
try {
const formData = new FormData(event.target);
const response = await fetch('/submit', {
method: 'POST',
body: formData
});
setStatus(response.ok ? 'Успешно отправлено' : 'Ошибка при отправке');
} catch (error) {
setStatus('Ошибка сети');
}
}
return html`
<form @submit=${handleSubmit}>
<input name="name" placeholder="Имя" />
<button type="submit">Отправить</button>
</form>
<p>${status}</p>
`;
}
customElements.define('form-component', component(FormComponent));
Особенности:
status обновляется асинхронно после
завершения запроса.await внутри обработчиков событий делает
код линейным и легко читаемым.useMemo и useCallbackДля оптимизации дорогостоящих вычислений можно комбинировать
асинхронные операции с хуками useMemo и
useCallback:
import { useMemo } from 'haunted';
function ComputedAsyncComponent({ input }) {
const processedData = useMemo(async () => {
const result = await fetch(`/process?value=${input}`);
return result.json();
}, [input]);
return html`<p>Результат: ${processedData}</p>`;
}
Примечание: Несмотря на то, что useMemo
обычно синхронен, асинхронные вычисления могут возвращать промис. В
таких случаях требуется дополнительно обрабатывать состояние загрузки,
чтобы корректно отображать данные после завершения промиса.
Асинхронные операции часто реализуются через таймеры. В Haunted можно
использовать setInterval или setTimeout внутри
useEffect:
function ClockComponent() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(interval);
}, []);
return html`<p>Текущее время: ${time}</p>`;
}
customElements.define('clock-component', component(ClockComponent));
Обращение внимания:
return функции эффекта предотвращает
утечки памяти.setTime внутри таймера обеспечивает
реактивное обновление шаблона каждую секунду.Когда несколько асинхронных операций могут завершаться в произвольном порядке, важно избегать гонок состояния. Один из подходов — использовать счётчик версий:
const [version, setVersion] = useState(0);
useEffect(() => {
let current = version;
async function fetchData() {
const result = await fetch(`/data?v=${current}`).then(r => r.json());
if (current === version) {
setData(result);
}
}
fetchData();
}, [version]);
Пояснение:
version гарантирует, что только результат
последнего запроса обновит состояние.Haunted позволяет напрямую использовать промисы внутри шаблонов с помощью условной проверки состояния:
const [dataPromise, setDataPromise] = useState(fetch('/items').then(r => r.json()));
return html`
<div>
${dataPromise
? html`<pre>${JSON.stringify(await dataPromise, null, 2)}</pre>`
: html`<p>Загрузка...</p>`}
</div>
`;
Рекомендация: Чтобы избежать ошибок рендеринга,
лучше обрабатывать промисы через состояние и useEffect, а
не напрямую в шаблоне, особенно для динамических обновлений.
useEffect для инициации асинхронных
процессов.try/catch.useState и
useReducer.Эти подходы позволяют создавать высокоотзывчивые компоненты с безопасной и предсказуемой асинхронной логикой, максимально используя преимущества реактивной архитектуры Haunted.