Загрузка данных

Atomico — это библиотека для создания веб-компонентов на базе JavaScript с реактивной моделью состояния. Загрузка данных в компонентах Atomico строится вокруг реактивных хуков и асинхронных функций, что позволяет создавать динамические интерфейсы без лишнего кода.

Хук useState и реактивное состояние

Для хранения загруженных данных используется хук useState:

import { useState, c } from "atomico";

function UserList() {
    const [users, setUsers] = useState([]);
    
    return (
        <host>
            <ul>
                {users.map(user => <li>{user.name}</li>)}
            </ul>
        </host>
    );
}

customElements.define("user-list", c(UserList));

useState создаёт реактивное состояние, автоматически обновляющее DOM при изменении значения. При загрузке данных важно правильно инициализировать состояние, чтобы избежать ошибок доступа к undefined при первом рендере.

Хук useEffect для побочных эффектов

Асинхронная загрузка данных обычно выполняется внутри useEffect, что позволяет запускать функции после монтирования компонента:

import { useEffect, useState, c } from "atomico";

function UserList() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);
    
    useEffect(() => {
        async function fetchUsers() {
            try {
                const response = await fetch("https://jsonplaceholder.typicode.com/users");
                const data = await response.json();
                setUsers(data);
            } catch (error) {
                console.error(error);
            } finally {
                setLoading(false);
            }
        }
        fetchUsers();
    }, []);
    
    return (
        <host>
            {loading ? <p>Загрузка...</p> : (
                <ul>
                    {users.map(user => <li>{user.name}</li>)}
                </ul>
            )}
        </host>
    );
}

customElements.define("user-list", c(UserList));

useEffect с пустым массивом зависимостей [] гарантирует, что загрузка данных произойдёт только один раз после первого рендера.

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

Для надёжной работы компонента рекомендуется хранить отдельные состояния:

  • loading — индикатор процесса загрузки.
  • error — информация об ошибке.
  • data — собственно, загруженные данные.
const [error, setError] = useState(null);

useEffect(() => {
    async function fetchData() {
        setLoading(true);
        setError(null);
        try {
            const res = await fetch("/api/data");
            if (!res.ok) throw new Error("Ошибка сети");
            const result = await res.json();
            setData(result);
        } catch (err) {
            setError(err.message);
        } finally {
            setLoading(false);
        }
    }
    fetchData();
}, []);

В JSX можно условно отображать различные состояния:

<host>
    {loading && <p>Загрузка...</p>}
    {error && <p style={{color: "red"}}>{error}</p>}
    {!loading && !error && data.map(item => <div>{item.title}</div>)}
</host>

Реактивная загрузка данных при изменении пропсов

Atomico позволяет наблюдать за изменениями входных свойств компонента через хук useWatch. Это особенно полезно для динамических запросов:

import { useWatch, useState, useEffect, c } from "atomico";

function PostList({ userId }) {
    const [posts, setPosts] = useState([]);
    const [loading, setLoading] = useState(false);

    useWatch(() => {
        if (!userId) return;
        setLoading(true);
        fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
            .then(res => res.json())
            .then(data => setPosts(data))
            .finally(() => setLoading(false));
    }, [userId]);

    return (
        <host>
            {loading ? <p>Загрузка постов...</p> : (
                <ul>{posts.map(post => <li>{post.title}</li>)}</ul>
            )}
        </host>
    );
}

customElements.define("post-list", c(PostList));

useWatch реагирует на изменения массива зависимостей и повторно запускает функцию загрузки, обеспечивая актуальные данные при смене пропсов.

Оптимизация и кеширование данных

Для снижения количества сетевых запросов можно реализовать простое кеширование:

const cache = new Map();

useEffect(() => {
    if (cache.has(userId)) {
        setPosts(cache.get(userId));
        setLoading(false);
        return;
    }

    setLoading(true);
    fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`)
        .then(res => res.json())
        .then(data => {
            cache.set(userId, data);
            setPosts(data);
        })
        .finally(() => setLoading(false));
}, [userId]);

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

Совмещение нескольких запросов

При необходимости загрузки нескольких ресурсов одновременно используется Promise.all:

useEffect(() => {
    setLoading(true);
    Promise.all([
        fetch("/api/users").then(res => res.json()),
        fetch("/api/posts").then(res => res.json())
    ]).then(([usersData, postsData]) => {
        setUsers(usersData);
        setPosts(postsData);
    }).catch(err => setError(err.message))
      .finally(() => setLoading(false));
}, []);

Это позволяет синхронизировать состояние нескольких источников данных и отображать их совместно в компоненте.

Поддержка отмены запросов

Чтобы избежать утечек памяти при быстром размонтировании компонентов, можно использовать AbortController:

useEffect(() => {
    const controller = new AbortController();

    fetch("/api/data", { signal: controller.signal })
        .then(res => res.json())
        .then(setData)
        .catch(err => {
            if (err.name !== "AbortError") setError(err.message);
        });

    return () => controller.abort();
}, []);

Такой подход предотвращает обновление состояния размонтированного компонента и исключает ошибки.

Итоговые рекомендации по загрузке данных

  • Использовать useState для хранения данных, состояния загрузки и ошибок.
  • Вызывать асинхронные функции через useEffect или useWatch.
  • Обрабатывать ошибки и отображать соответствующие сообщения.
  • При необходимости кешировать данные для уменьшения числа запросов.
  • Для сложных зависимостей и множественных источников данных применять Promise.all.
  • Отменять асинхронные операции при размонтировании компонента с помощью AbortController.

Эти подходы создают устойчивые и отзывчивые веб-компоненты на Atomico, готовые к работе с динамическими источниками данных.