Предзагрузка данных

В Universal Router предзагрузка данных (data preloading) позволяет получать необходимые ресурсы до того, как компонент будет отрендерен. Это особенно важно для SPA-приложений, где требуется минимизировать задержку отображения страницы и обеспечить пользователю мгновенный доступ к контенту.

Асинхронные обработчики маршрутов

Каждый маршрут в Universal Router может содержать асинхронный обработчик через функцию action. Она может возвращать данные напрямую или через промис. Пример базового маршрута с предзагрузкой данных:

import UniversalRouter from 'universal-router';

const routes = [
  {
    path: '/users/:id',
    async action({ params, query }) {
      const response = await fetch(`/api/users/${params.id}`);
      const user = await response.json();
      return { user };
    }
  }
];

const router = new UniversalRouter(routes);
router.resolve({ pathname: '/users/42' }).then(result => {
  console.log(result.user);
});

Здесь action асинхронно загружает данные пользователя и возвращает объект, который затем может использоваться для рендеринга страницы.

Параметры маршрута и их использование

Universal Router передает в action объект с ключевыми параметрами:

  • pathname — текущий путь.
  • params — параметры маршрута, определённые в пути (:id, :slug и т.д.).
  • query — объект с параметрами запроса.
  • route — текущий маршрут.
  • context — объект контекста, передаваемый в resolve.

Использование params и context позволяет создавать универсальные функции предзагрузки данных, которые могут использоваться на нескольких маршрутах.

async function loadUser({ params }) {
  const response = await fetch(`/api/users/${params.id}`);
  return response.json();
}

const routes = [
  {
    path: '/users/:id',
    async action(ctx) {
      const user = await loadUser(ctx);
      return { user };
    }
  }
];

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

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

const routes = [
  {
    path: '/dashboard',
    async action() {
      const [usersResponse, statsResponse] = await Promise.all([
        fetch('/api/users'),
        fetch('/api/stats')
      ]);
      const users = await usersResponse.json();
      const stats = await statsResponse.json();
      return { users, stats };
    }
  }
];

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

Использование контекста для кэширования

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

const context = { cache: new Map() };

const routes = [
  {
    path: '/products/:id',
    async action({ params, context }) {
      if (context.cache.has(params.id)) {
        return context.cache.get(params.id);
      }
      const response = await fetch(`/api/products/${params.id}`);
      const product = await response.json();
      context.cache.set(params.id, { product });
      return { product };
    }
  }
];

const router = new UniversalRouter(routes, { context });

В этом примере данные продукта кэшируются в context.cache и используются при повторных обращениях к маршруту.

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

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

const routes = [
  {
    path: '/profile/:id',
    async action({ params }) {
      const response = await fetch(`/api/profile/${params.id}`);
      const profile = await response.json();
      const ProfileComponent = (await import('./Profile.js')).default;
      return { component: ProfileComponent, profile };
    }
  }
];

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

Глобальная предзагрузка данных

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

async function preloadAuth({ context }) {
  if (!context.user) {
    const response = await fetch('/api/auth/me');
    context.user = await response.json();
  }
}

const routes = [
  {
    path: '/settings',
    async action(ctx) {
      await preloadAuth(ctx);
      return { settings: await fetchSettings() };
    }
  }
];

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

Поддержка серверного рендеринга (SSR)

Предзагрузка данных особенно актуальна для SSR. Universal Router позволяет резолвить маршруты на сервере, загружая все данные до передачи HTML клиенту:

const context = {};
const routeData = await router.resolve({ pathname: '/users/1', context });
// routeData содержит все данные для рендеринга на сервере

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

Рекомендации по организации предзагрузки

  • Использовать отдельные функции загрузки данных вместо логики прямо в маршруте.
  • Кэшировать результаты в context для повторного использования.
  • Разделять предзагрузку данных и динамическую подгрузку компонентов.
  • Использовать Promise.all для параллельной загрузки нескольких ресурсов.
  • Продумывать обработку ошибок внутри action для предотвращения прерывания маршрута.

Эффективная предзагрузка данных в Universal Router делает SPA более отзывчивым, ускоряет время первого рендеринга и облегчает интеграцию с серверным рендерингом.