Обработка загрузки и ошибок

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


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

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

  • loading — данные еще не получены, выполняется запрос.
  • success — данные успешно загружены.
  • error — произошла ошибка при загрузке данных.

Пример реализации:

import { useState, useEffect } from 'inferno-hooks';

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

    useEffect(() => {
        fetchData()
            .then(response => {
                setData(response);
                setLoading(false);
            })
            .catch(err => {
                setError(err);
                setLoading(false);
            });
    }, [fetchData]);

    if (loading) {
        return <div>Загрузка...</div>;
    }

    if (error) {
        return <div>Ошибка: {error.message}</div>;
    }

    return <div>Данные: {JSON.stringify(data)}</div>;
}

Ключевые моменты:

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

Асинхронные функции и обработка ошибок

Для асинхронных запросов в Inferno чаще всего используют fetch API или сторонние библиотеки вроде Axios. Любой промис должен содержать блок .catch для перехвата ошибок. Альтернатива — использование async/await с конструкцией try/catch:

async function loadUserData() {
    try {
        const response = await fetch('/api/user');
        if (!response.ok) throw new Error('Сетевая ошибка');
        const result = await response.json();
        return result;
    } catch (err) {
        console.error('Ошибка при загрузке данных:', err);
        throw err;
    }
}

Использование такого подхода обеспечивает:

  • Четкую диагностику ошибок.
  • Возможность передачи ошибок компоненту для отображения пользователю.
  • Унифицированную обработку исключений в разных частях приложения.

Специальные компоненты для обработки ошибок

Inferno поддерживает паттерн Error Boundaries, который позволяет перехватывать ошибки в дочерних компонентах и предотвращать крах всего приложения. Реализация включает метод componentDidCatch в классовых компонентах:

import { Component } from 'inferno';

class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state = { hasError: false, error: null };
    }

    componentDidCatch(error) {
        this.setState({ hasError: true, error });
        console.error('Ошибка в компоненте:', error);
    }

    render() {
        if (this.state.hasError) {
            return <div>Произошла ошибка: {this.state.error.message}</div>;
        }

        return this.props.children;
    }
}

Особенности:

  • Error Boundary не перехватывает ошибки в асинхронных вызовах вне рендера (нужно дополнительно обрабатывать через try/catch).
  • Может использоваться для оборачивания любых частей приложения, где вероятность ошибки выше.

Оптимизация UX при загрузке

Для улучшения взаимодействия с пользователем часто используют скелетоны или индикаторы прогресса:

function SkeletonLoader({ width, height }) {
    return (
        <div style={{
            width: width || '100%',
            height: height || '1em',
            backgroundColor: '#eee',
            borderRadius: '4px',
            animation: 'pulse 1.5s infinite'
        }} />
    );
}
  • Анимация pulse создается с помощью CSS для плавного эффекта.
  • Позволяет показать пользователю «скелет» будущего контента вместо пустого экрана.
  • Сокращает ощущение задержки при загрузке данных.

Комбинирование загрузки и ошибок

Частой практикой является объединение логики загрузки и обработки ошибок в один контейнерный компонент:

function AsyncContainer({ fetchData, children }) {
    const [state, setState] = useState({ data: null, loading: true, error: null });

    useEffect(() => {
        fetchData()
            .then(data => setState({ data, loading: false, error: null }))
            .catch(error => setState({ data: null, loading: false, error }));
    }, [fetchData]);

    if (state.loading) return <SkeletonLoader />;
    if (state.error) return <div>Ошибка: {state.error.message}</div>;

    return children(state.data);
}

Преимущества такого подхода:

  • Изоляция логики загрузки от визуальной части.
  • Повторное использование компонента для разных источников данных.
  • Упрощение тестирования и поддержки кода.

Итоговые рекомендации

  • Всегда отделять состояние загрузки, ошибки и успешной загрузки.
  • Использовать Error Boundaries для защиты ключевых частей приложения.
  • Применять скелетоны и индикаторы для улучшения UX.
  • Асинхронные операции оборачивать в try/catch или .catch для корректного перехвата ошибок.
  • Централизовать обработку состояния в контейнерных компонентах для упрощения архитектуры.