Загрузка данных и индикаторы прогресса

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


Асинхронная загрузка данных

В Riot.js асинхронные операции обычно выполняются в методах жизненного цикла компонента, таких как mounted() или onBeforeMount(). Для загрузки данных используют стандартные возможности JavaScript — fetch, axios или другие HTTP-клиенты.

Пример загрузки данных через fetch:

<user-list>
  <ul>
    <li each={user in users}>{user.name}</li>
  </ul>

  <script>
    export default {
      users: [],
      loading: true,

      async onBeforeMount() {
        try {
          const response = await fetch('https://jsonplaceholder.typicode.com/users');
          const data = await response.json();
          this.users = data;
        } catch (error) {
          console.error('Ошибка при загрузке данных:', error);
        } finally {
          this.loading = false;
        }
      }
    }
  </script>
</user-list>

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

  • users — реактивное свойство, автоматически обновляющее DOM при изменении.
  • loading — флаг состояния загрузки, который можно использовать для отображения индикатора прогресса.
  • async onBeforeMount() гарантирует, что данные начнут загружаться перед рендером компонента.

Индикаторы прогресса

Отображение индикаторов прогресса связано с состоянием компонента. Riot.js поддерживает условный рендеринг с использованием директив if и show.

Пример использования индикатора:

<user-list>
  <div if={loading}>Загрузка данных...</div>
  <ul if={!loading}>
    <li each={user in users}>{user.name}</li>
  </ul>

  <script>
    export default {
      users: [],
      loading: true,

      async onBeforeMount() {
        try {
          const response = await fetch('https://jsonplaceholder.typicode.com/users');
          const data = await response.json();
          this.users = data;
        } catch (error) {
          console.error('Ошибка при загрузке данных:', error);
        } finally {
          this.loading = false;
        }
      }
    }
  </script>
</user-list>

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


Интеграция с прогресс-барами

Для реализации полосы прогресса можно создать отдельный компонент:

<progress-bar>
  <div class="progress-container">
    <div class="progress" style="width: {progress}%"></div>
  </div>

  <script>
    export default {
      progress: 0
    }
  </script>

  <style>
    .progress-container {
      width: 100%;
      background: #f0f0f0;
      height: 8px;
      border-radius: 4px;
      overflow: hidden;
    }
    .progress {
      height: 100%;
      background: #4caf50;
      transition: width 0.3s ease;
    }
  </style>
</progress-bar>

В родительском компоненте:

<user-loader>
  <progress-bar progress={progress}></progress-bar>
  <ul if={!loading}>
    <li each={user in users}>{user.name}</li>
  </ul>

  <script>
    import ProgressBar from './progress-bar.riot';

    export default {
      users: [],
      loading: true,
      progress: 0,

      async onBeforeMount() {
        const response = await fetch('https://jsonplaceholder.typicode.com/users');
        const reader = response.body.getReader();
        const contentLength = +response.headers.get('Content-Length');
        let receivedLength = 0;
        let chunks = [];

        while(true) {
          const {done, value} = await reader.read();
          if(done) break;
          chunks.push(value);
          receivedLength += value.length;
          this.progress = Math.round((receivedLength / contentLength) * 100);
        }

        const chunksAll = new Uint8Array(receivedLength);
        let position = 0;
        for(let chunk of chunks) {
          chunksAll.set(chunk, position);
          position += chunk.length;
        }

        const result = new TextDecoder("utf-8").decode(chunksAll);
        this.users = JSON.parse(result);
        this.loading = false;
      }
    }
  </script>
</user-loader>

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


Использование сторонних библиотек

Riot.js совместим с внешними библиотеками, такими как axios и nprogress. Для интеграции достаточно управлять состоянием компонентов через методы жизненного цикла и реактивные свойства:

import axios from 'axios';
import NProgress from 'nprogress';

export default {
  users: [],
  loading: false,

  async onBeforeMount() {
    NProgress.start();
    this.loading = true;
    try {
      const {data} = await axios.get('https://jsonplaceholder.typicode.com/users');
      this.users = data;
    } catch (error) {
      console.error(error);
    } finally {
      this.loading = false;
      NProgress.done();
    }
  }
}

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


Реактивные подходы к UX

Ключевой особенностью Riot.js является реактивность, позволяющая связывать состояние загрузки, прогресс и контент напрямую с DOM. Это делает компоненты:

  • Простыми для поддержки.
  • Читаемыми и модульными.
  • Легко расширяемыми для разных типов загрузки — синхронной, асинхронной или потоковой.

Использование реактивных свойств loading, progress и массивов данных позволяет создавать интерфейсы с плавными переходами и визуальной обратной связью без сложных структур и дополнительных библиотек.