Хуки useState и useEffect

Atomico — современная библиотека для создания веб-компонентов на JavaScript с реактивным подходом, вдохновлённым концепциями React. Основу реактивного поведения в Atomico составляют хуки, среди которых ключевую роль играют useState и useEffect. Они позволяют управлять состоянием компонента и отслеживать побочные эффекты.


useState

useState используется для создания локального состояния внутри компонента. Основной принцип: хук возвращает массив из двух элементов — текущего значения состояния и функции для его обновления.

Синтаксис:

import { useState, html, component } from "atomico";

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

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

export default component(Counter);

Особенности useState в Atomico:

  • Реактивность: Любое изменение состояния автоматически инициирует повторный рендер компонента.
  • Инициализация: Аргумент, переданный в useState, используется как начальное значение. Можно передавать функции для ленивой инициализации:
const [data, setData] = useState(() => expensiveComputation());
  • Функциональное обновление: Для обновления состояния можно использовать функцию, получающую текущее значение:
setCount(prev => prev + 1);

useEffect

useEffect предназначен для управления побочными эффектами: запросы к API, подписки, таймеры, работа с DOM вне шаблона компонента. Он запускается после рендера компонента и может зависеть от определённых значений состояния.

Синтаксис:

import { useEffect } from "atomico";

useEffect(() => {
    console.log("Компонент смонтирован или обновлён");

    return () => {
        console.log("Компонент размонтирован или эффект обновлён");
    };
}, [dependencies]);

Особенности useEffect в Atomico:

  • Массив зависимостей: Эффект выполняется только при изменении указанных значений.
  • Очистка эффектов: Функция, возвращаемая из useEffect, вызывается перед удалением компонента или перед следующим вызовом эффекта. Это особенно важно для таймеров и подписок.
  • Отслеживание состояния: Для реактивного поведения часто используют useState совместно с useEffect:
const [count, setCount] = useState(0);

useEffect(() => {
    const timer = setInterval(() => setCount(prev => prev + 1), 1000);
    return () => clearInterval(timer);
}, []);

Взаимодействие useState и useEffect

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

Пример динамического счётчика с синхронизацией с локальным хранилищем:

const [count, setCount] = useState(
    () => Number(localStorage.getItem("count")) || 0
);

useEffect(() => {
    localStorage.setItem("count", count);
}, [count]);

В этом примере:

  • useState инициализирует состояние из localStorage.
  • useEffect отслеживает изменения count и синхронизирует их с хранилищем.

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

  1. Минимизировать побочные эффекты в рендере: Всё, что влияет на внешний мир или требует асинхронной работы, должно быть в useEffect.
  2. Оптимизация зависимостей: Указывать только те переменные, от которых действительно зависит эффект.
  3. Функциональные обновления для сложных состояний: Это предотвращает гонки при асинхронных операциях.
  4. Избегать лишних вызовов эффекта: При работе с API использовать abortController или очистку таймеров, чтобы избежать утечек памяти.

Практические паттерны

  • Подписка на события:
useEffect(() => {
    const handler = e => console.log(e.type);
    window.addEventListener("click", handler);

    return () => window.removeEventListener("click", handler);
}, []);
  • Асинхронный запрос данных:
useEffect(() => {
    let mounted = true;

    async function fetchData() {
        const response = await fetch("/api/data");
        const result = await response.json();
        if (mounted) setData(result);
    }

    fetchData();

    return () => {
        mounted = false;
    };
}, []);
  • Комбинированное управление состоянием и эффектами:
const [input, setInput] = useState("");
const [debounced, setDebounced] = useState("");

useEffect(() => {
    const timer = setTimeout(() => setDebounced(input), 300);
    return () => clearTimeout(timer);
}, [input]);

Хуки useState и useEffect образуют ядро реактивного подхода в Atomico. Правильное понимание их взаимодействия позволяет создавать компоненты, управляемые состоянием и чисто синхронизированные с внешним окружением.