Управление состоянием загрузки

Основы управления состоянием

В Preact управление состоянием компонентов осуществляется с помощью хука useState. Для управления состоянием загрузки это основной инструмент, позволяющий отслеживать процесс асинхронных операций, таких как запросы к API. Создание состояния загрузки обычно выглядит следующим образом:

import { h } from 'preact';
import { useState, useEffect } from 'preact/hooks';

function DataLoader() {
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

    return (
        <div>
            {loading && <p>Загрузка...</p>}
            {error && <p>Ошибка: {error}</p>}
            {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
        </div>
    );
}

Здесь loading отражает текущее состояние компонента, позволяя условно отображать индикатор загрузки.

Асинхронные операции и useEffect

Для выполнения асинхронных задач применяют хук useEffect. Он позволяет запускать побочные эффекты, такие как сетевые запросы, при монтировании компонента или изменении зависимостей.

useEffect(() => {
    setLoading(true);
    fetch('https://api.example.com/data')
        .then(response => response.json())
        .then(result => setData(result))
        .catch(err => setError(err.message))
        .finally(() => setLoading(false));
}, []);

Ключевой момент: состояние loading должно изменяться в начале асинхронного запроса и сбрасываться в блоке finally, чтобы корректно отражать завершение операции независимо от результата.

Паттерн “Лоадер”

Для повторного использования логики загрузки часто применяют паттерн “Лоадер” — отдельный компонент, оборачивающий асинхронный вызов и предоставляющий универсальный интерфейс для отображения состояний:

function Loader({ promise, children }) {
    const [loading, setLoading] = useState(true);
    const [data, setData] = useState(null);
    const [error, setError] = useState(null);

    useEffect(() => {
        promise
            .then(result => setData(result))
            .catch(err => setError(err.message))
            .finally(() => setLoading(false));
    }, [promise]);

    if (loading) return <p>Загрузка...</p>;
    if (error) return <p>Ошибка: {error}</p>;
    return children(data);
}

Использование:

<Loader promise={fetch('https://api.example.com/data').then(res => res.json())}>
    {data => <pre>{JSON.stringify(data, null, 2)}</pre>}
</Loader>

Такой подход отделяет логику загрузки от визуального представления, упрощает поддержку и повторное использование кода.

Управление несколькими параллельными загрузками

Если необходимо выполнять несколько параллельных запросов, состояние загрузки может быть представлено объектом или массивом. Пример с объектом:

function MultiLoader() {
    const [loading, setLoading] = useState({ users: false, posts: false });
    const [users, setUsers] = useState([]);
    const [posts, setPosts] = useState([]);

    useEffect(() => {
        setLoading(prev => ({ ...prev, users: true }));
        fetch('/api/users')
            .then(res => res.json())
            .then(setUsers)
            .finally(() => setLoading(prev => ({ ...prev, users: false })));

        setLoading(prev => ({ ...prev, posts: true }));
        fetch('/api/posts')
            .then(res => res.json())
            .then(setPosts)
            .finally(() => setLoading(prev => ({ ...prev, posts: false })));
    }, []);

    return (
        <div>
            {loading.users && <p>Загрузка пользователей...</p>}
            {loading.posts && <p>Загрузка постов...</p>}
            <pre>{JSON.stringify({ users, posts }, null, 2)}</pre>
        </div>
    );
}

Использование объекта позволяет гибко управлять состоянием нескольких независимых загрузок и точно отображать прогресс каждой из них.

Оптимизация ререндеров

Для минимизации лишних ререндеров при изменении состояния загрузки важно обновлять только необходимые части состояния. Например, при работе с объектом состояния следует использовать функциональный вариант setState:

setLoading(prev => ({ ...prev, users: false }));

Это гарантирует, что изменения будут объединяться, а не перезаписывать весь объект.

Индикация прогресса загрузки

Для больших данных или медленных соединений полезно отображать прогресс. В Preact можно реализовать это с помощью дополнительного состояния progress:

const [progress, setProgress] = useState(0);

useEffect(() => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', '/large-file');
    xhr.onprogr ess = event => {
        if (event.lengthComputable) {
            setProgress(Math.round((event.loaded / event.total) * 100));
        }
    };
    xhr.onl oad = () => setProgress(100);
    xhr.send();
}, []);

Интерфейс может включать прогресс-бар или процент загрузки, что улучшает пользовательский опыт.

Итоговый подход

Эффективное управление состоянием загрузки в Preact строится на комбинации useState, useEffect и аккуратного разделения логики и представления. Паттерн “Лоадер”, объекты состояния для параллельных загрузок и прогресс-индикация создают надежную архитектуру для асинхронных операций и делают код предсказуемым и легко поддерживаемым.