Состояния загрузки и ошибок

SkateJS предоставляет гибкий и декларативный подход к созданию веб-компонентов, включая управление их состояниями. Одной из важных задач при разработке компонентов является обработка состояний загрузки и ошибок, особенно при работе с асинхронными операциями, такими как запросы к API или динамическая загрузка данных.

Определение состояния в компоненте

SkateJS использует реактивные свойства для отслеживания состояния компонента. Свойства объявляются через объект props и могут включать различные типы значений, включая boolean, string, number и object. Для управления состояниями загрузки и ошибок чаще всего используют отдельные свойства:

import { define, props, h, Component } from 'skatejs';

class DataLoader extends Component {
  static props = {
    loading: props.boolean({ default: false }),
    error: props.string(),
    data: props.array()
  };

  connectedCallback() {
    super.connectedCallback();
    this.fetchData();
  }

  async fetchData() {
    this.loading = true;
    this.error = '';
    try {
      const response = await fetch('https://api.example.com/items');
      if (!response.ok) throw new Error(`Ошибка ${response.status}`);
      this.data = await response.json();
    } catch (err) {
      this.error = err.message;
    } finally {
      this.loading = false;
    }
  }

  render() {
    if (this.loading) return h('p', null, 'Загрузка данных...');
    if (this.error) return h('p', { style: 'color: red;' }, `Ошибка: ${this.error}`);
    return h('ul', null, this.data.map(item => h('li', null, item.name)));
  }
}

define('data-loader', DataLoader);

В этом примере показана типичная структура для управления состояниями:

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

Реактивность и обновление интерфейса

SkateJS автоматически отслеживает изменения свойств и вызывает метод render. Это позволяет легко переключать отображение компонента в зависимости от текущего состояния:

  • При loading = true отображается индикатор загрузки.
  • При наличии значения в error выводится сообщение об ошибке.
  • В остальных случаях отображаются полученные данные.

Такой подход обеспечивает декларативное управление состояниями без необходимости вручную манипулировать DOM.

Пользовательские индикаторы загрузки

Можно создавать более сложные индикаторы загрузки с анимацией или прогресс-баром. Пример с прогресс-баром:

render() {
  if (this.loading) {
    return h('div', { class: 'loader' },
      h('div', { class: 'progress' })
    );
  }
  if (this.error) {
    return h('p', { class: 'error' }, `Ошибка: ${this.error}`);
  }
  return h('ul', null, this.data.map(item => h('li', null, item.name)));
}

Классы .loader и .progress могут быть определены через CSS для отображения анимации. SkateJS оставляет полную свободу в оформлении и позволяет интегрировать любые сторонние библиотеки для визуализации загрузки.

Обработка нескольких состояний одновременно

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

static props = {
  state: props.object({ default: () => ({ loading: false, error: '', data: [] }) })
};

async fetchData() {
  this.state = { ...this.state, loading: true, error: '' };
  try {
    const response = await fetch('https://api.example.com/items');
    if (!response.ok) throw new Error(`Ошибка ${response.status}`);
    this.state = { ...this.state, data: await response.json() };
  } catch (err) {
    this.state = { ...this.state, error: err.message };
  } finally {
    this.state = { ...this.state, loading: false };
  }
}

render() {
  const { loading, error, data } = this.state;
  if (loading) return h('p', null, 'Загрузка данных...');
  if (error) return h('p', { style: 'color: red;' }, `Ошибка: ${error}`);
  return h('ul', null, data.map(item => h('li', null, item.name)));
}

Использование объекта state позволяет централизованно управлять всеми состояниями компонента, особенно если их становится несколько (например, loadingProfile, loadingPosts, errorProfile, errorPosts).

Динамическая обработка ошибок

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

render() {
  if (this.state.loading) return h('p', null, 'Загрузка...');
  if (this.state.error) {
    switch (this.state.error) {
      case '404':
        return h('p', { style: 'color: orange;' }, 'Данные не найдены.');
      case '500':
        return h('p', { style: 'color: red;' }, 'Серверная ошибка.');
      default:
        return h('p', { style: 'color: gray;' }, `Неизвестная ошибка: ${this.state.error}`);
    }
  }
  return h('ul', null, this.state.data.map(item => h('li', null, item.name)));
}

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

Асинхронные операции и повторные попытки

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

async fetchData(retries = 3) {
  this.state = { ...this.state, loading: true, error: '' };
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const response = await fetch('https://api.example.com/items');
      if (!response.ok) throw new Error(`Ошибка ${response.status}`);
      this.state = { ...this.state, data: await response.json(), loading: false };
      return;
    } catch (err) {
      if (attempt === retries) {
        this.state = { ...this.state, error: err.message, loading: false };
      }
    }
  }
}

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

Взаимодействие с пользовательскими событиями

Можно связывать состояния загрузки и ошибок с событиями, например, кнопкой «Повторить»:

render() {
  const { loading, error, data } = this.state;
  if (loading) return h('p', null, 'Загрузка...');
  if (error) {
    return h('div', null,
      h('p', { style: 'color: red;' }, `Ошибка: ${error}`),
      h('button', { onclick: () => this.fetchData() }, 'Повторить')
    );
  }
  return h('ul', null, data.map(item => h('li', null, item.name)));
}

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

Выводы о подходе

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