Улучшение отзывчивости веб-интерфейса часто начинается с оптимизации того, как ваш фронтенд взаимодействует с бэкендом. В нашем основанном на Vue.js Semaphore UI мы внедрили несколько техник, чтобы сделать загрузку данных быстрее и плавнее для пользователей. В этом посте мы делимся тремя наиболее значительными изменениями, которые мы реализовали в версии 2.14.


Почему оптимизировать использование API?

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


1. Параллельные запросы с Promise.all()

Проблема: Последовательные запросы

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

// Последовательный подход (медленно 😞)
async function fetchData() {
  const user = await getUser();
  const inventory = await getInventory(projectID);
  const templates = await getTemplates(projectID);
  return { user, inventory, templates };
}

Здесь каждый запрос ждет завершения предыдущего. Если каждый вызов занимает 200 мс, общее время составит 600 мс — заметная задержка.

Решение: Параллельное выполнение

Vue.js использует асинхронные возможности JavaScript, поэтому мы можем отправлять несколько запросов одновременно, используя Promise.all():

// Параллельный подход (быстро ⚡)
async function fetchDataParallel() {
  const [user, inventory, templates] = await Promise.all([
    getUser(),
    getInventory(projectID),
    getTemplates(projectID),
  ]);
  return { user, inventory, templates };
}

Группируя независимые запросы, все три вызова выполняются одновременно. Если каждый занимает 200 мс, общее время сокращается до 200 мс!

Интеграция с Vue

В компоненте Vue используйте этот шаблон в хуках жизненного цикла или методах:

export default {
  props: {
    projectID: Number,
  },
  watch: {
    async projectID() {
      await this.fetchDataParallel();
    },
  },
  async created() {
    await this.fetchDataParallel();
  },
  methods: {
    async fetchDataParallel() {
      try {
        const [
          this.user, 
          this.inventory,
          this.templates
        ] = await Promise.all([
          getUser(),
          getInventory(this.projectID),
          getTemplates(this.projectID),
        ]);
      } catch (error) {
        console.error("Не удалось загрузить данные:", error);
      }
    };
  }

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


2. Повторное использование кэшированных ответов API

Проблема: Избыточная выборка

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

Решение: Кэширование и повторное использование

Храните ответы API и повторно используйте их до тех пор, пока они не будут недействительными. Реактивная система Vue делает это простым с помощью централизованного хранилища (например, Pinia или Vuex):

Шаг 1: Создайте кэш-хранилище

// stores/apiCache.js
import { defineStore } from 'pinia';

export const useApiCache = defineStore('apiCache', {
  state: () => ({
    project: null,
    templates: {},
  }),
  actions: {
    async fetchProject() {
      if (!this.project) {
        this.project = await getProject();
      }
      return this.project;
    },
    async fetchTemplates(id) {
      if (!this.templates[id]) {
        this.templates[id] = await fetchTemplates(id);
      }
      return this.templates[id];
    },
  },
});

Шаг 2: Используйте хранилище в компонентах

// Component.vue
import { useApiCache } from '@/stores/apiCache';

export default {
  setup() {
    const apiCache = useApiCache();

    // Повторно использует кэшированного пользователя или запрашивает один раз
    const user = apiCache.fetchUser();

    return { user };
  },
};

Совет: В Semaphore UI мы пока не используем Pinia, но планируем перейти на него в ближайшее время.

Стратегии недействительности кэша

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

Проблема: Невозможность взаимодействия с интерфейсом во время загрузки

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

Решение: Загрузчики скелетов

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


Как добавить загрузчики скелетов в Vue.js

Шаг 1: Выберите компонент скелета

В Semaphore UI мы используем фреймворк Vuetify. Он уже включает компонент загрузчика скелета.

<v-skeleton-loader
  type="
    table-heading,
    image,
    list-item-two-line,
    list-item-two-line,
    list-item-two-line"
></v-skeleton-loader>

Шаг 2: Интеграция с асинхронной выборкой данных Используйте состояние загрузки, чтобы переключаться между скелетами и реальным контентом:

// Component.vue
export default {
  data() {
    return {
      isLoading: true,
      user: null,
      templates: [],
    };
  },
  async created() {
    this.isLoading = true;
    try {
      [this.user, this.templates] = await Promise.all([
        fetchUser(),
        fetchTemplates(),
      ]);
    } finally {
      this.isLoading = false;
    }
  },
};

Шаг 3: Условный рендеринг скелетов

<template>
  <div class="user-profile">
    <v-skeleton-loader
      v-if="isLoading"
      type="
        table-heading,
        image,
        list-item-two-line,
        list-item-two-line,
        list-item-two-line"
    ></v-skeleton-loader>
    <v-card v-else>
      <h2>{{ user.name }}</h2>

      <v-card v-for="tpl in templates" :key="tpl.id">
        <v-card-title>{{ tpl.name }}</v-card-title>
        <v-card-text>{{ tpl.description }}</v-card-text>
      </v-card>
    </v-card>
  </div>
</template>

Расширенные советы по дизайну загрузчиков

  1. Соответствие структуре контента: Разработайте скелеты так, чтобы они отражали ваш фактический макет (например, заполнители изображений, строки текста).
  2. Приоритизируйте контент выше линии сгиба: Сначала показывайте загрузчики для видимого контента, пока загружаются данные в фоновом режиме.
  3. Сочетайте с кэшированием: Если кэшированные данные существуют, пропустите скелеты и сразу покажите данные.

Почему это важно


Подводя итоги

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

  1. Быстрая начальная загрузка: Параллельные запросы значительно сократили время загрузки данных.
  2. Плавная навигация: Повторное использование ответов уменьшило избыточные сетевые вызовы.
  3. Увеличение вовлеченности пользователей: Загрузчики скелетов улучшили визуальную обратную связь и восприятие скорости.

Оптимизация использования API — это не только вопрос производительности, но и улучшение пользовательского опыта. Мы будем продолжать совершенствовать наш фронтенд, чтобы сделать Semaphore UI быстрее и дружелюбнее с каждым обновлением.