Haunted — это библиотека для создания реактивных веб-компонентов на базе стандартных Custom Elements, использующая подход, близкий к React Hooks. Одной из ключевых возможностей является работа с производными значениями, позволяющими вычислять данные на основе состояния и автоматически обновлять интерфейс при их изменении.
Производные значения (derived values) представляют собой
вычисляемые состояния, которые зависят от одного или
нескольких реактивных состояний (state). Основное отличие
от обычных переменных в том, что они автоматически
пересчитываются, когда изменяются зависимые значения. Это
избавляет от необходимости вручную синхронизировать интерфейс с
данными.
В Haunted производные значения создаются с помощью функции
useMemo или сторонних реактивных решений, интегрируемых с
хуками Haunted.
import { html, component, useState, useMemo } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
const double = useMemo(() => count * 2, [count]);
return html`
<div>Счётчик: ${count}</div>
<div>Удвоенное значение: ${double}</div>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
`;
}
customElements.define('my-counter', component(Counter));
В этом примере double является производным
значением, которое автоматически пересчитывается при изменении
count. Массив зависимостей [count] указывает,
от каких состояний зависит вычисление.
Производные значения имеют динамическую реактивность, основанную на зависимостях. Любая переменная, включённая в массив зависимостей, служит триггером пересчёта. Если зависимости не указаны, вычисление выполняется при каждом рендере, что снижает производительность при сложных вычислениях.
const fullName = useMemo(
() => `${firstName} ${lastName}`,
[firstName, lastName]
);
Важным аспектом является правильное указание зависимостей. Пропуск зависимой переменной может привести к несвоевременному обновлению интерфейса, а избыточные зависимости — к лишним пересчётам.
Haunted позволяет использовать useMemo и для асинхронных
операций, однако необходимо учитывать, что useMemo
не предназначен для промисов напрямую. В таких случаях
целесообразно использовать useEffect в сочетании с
состоянием.
import { html, component, useState, useEffect } from 'haunted';
function AsyncData({ url }) {
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
if (!cancelled) setData(result);
}
fetchData();
return () => { cancelled = true; };
}, [url]);
return html`
<div>${data ? JSON.stringify(data) : 'Загрузка...'}</div>
`;
}
customElements.define('async-data', component(AsyncData));
В этом примере состояние data является производным
результатом асинхронного запроса, зависящего от url. Такой
подход обеспечивает реактивность даже при работе с внешними
ресурсами.
Производные значения могут зависеть от других производных значений, создавая цепочки вычислений. Haunted гарантирует, что обновления происходят последовательно, начиная с базовых состояний и заканчивая зависимыми вычислениями.
const a = useState(1);
const b = useMemo(() => a * 2, [a]);
const c = useMemo(() => b + 3, [b]);
В этом примере c автоматически обновляется при изменении
a, потому что b также обновляется и является
зависимостью c.
Использование useMemo помогает избежать ненужных
пересчётов при рендере. Для сложных вычислений, таких как
фильтрация массивов или сложные математические операции, это критично
для производительности.
const filteredItems = useMemo(
() => items.filter(item => item.active),
[items]
);
Здесь фильтрация выполняется только при изменении массива
items, что снижает нагрузку на DOM при частых рендерах.
useEffect, а не в useMemo.useMemo не кэширует результат
глобально, он предназначен исключительно для оптимизации
локального рендера компонента.const sortedData = useMemo(
() => data.slice().sort((a, b) => a.value - b.value),
[data]
);
const total = useMemo(
() => cartItems.reduce((sum, item) => sum + item.price, 0),
[cartItems]
);
const statusMessage = useMemo(() => {
if (isLoading) return 'Загрузка...';
if (error) return `Ошибка: ${error}`;
return 'Готово';
}, [isLoading, error]);
Эти подходы делают интерфейс Haunted компонентов чётко синхронизированным с состоянием и повышают читаемость кода, разделяя данные и их производные представления.