routeLoader$()

Загрузчики маршрутов загружают данные на сервер, чтобы они стали доступны для использования в компонентах Qwik. Они срабатывают, когда происходит навигация SPA/MPA, чтобы их могли вызывать компоненты Qwik во время рендера.

Загрузчики маршрута могут быть объявлены только внутри папки src/routes, в файле layout.tsx или index.tsx, и они ДОЛЖНЫ быть экспортированы.

Если вы хотите управлять общими многократно используемыми routeLoaders$, необходимо, чтобы эта функция была реэкспортирована из файла 'layout.tsx' или 'index.tsx' существующего маршрута, иначе она не будет запущена или выбросит исключение. Для получения дополнительной информации обратитесь к разделу рецептов.

src/routes/product/[productId]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductDetails = routeLoader$(async (requestEvent) => {
  // Этот код выполняется только на сервере, после каждой навигации
  const res = await fetch(`https://.../products/${requestEvent.params.productId}`);
  const product = await res.json();
  return product as Product;
});
 
export default component$(() => {
  // Чтобы получить доступ к данным `routeLoader$` в компоненте Qwik, необходимо вызвать хук.
  const signal = useProductDetails(); // Readonly<Signal<Product>>
  return <p>Наименование товара: {signal.value.product.name}</p>;
});

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

Вы не должны использовать routeLoader$ для создания REST API, для этого лучше использовать Endpoint, который позволяет жёстко контролировать заголовки и тело ответа.

Несколько routeLoader$-ов

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

src/routes/layout.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
import { Footer } from '../components/footer.tsx';
 
export const useProductData = routeLoader$(async () => {
  const res = await fetch('https://.../product');
  const product = await res.json() as Product;
  return product;
});
 
export default component$(() => {
  const signal = useProductData();
  return (
    <main>
      <Slot />
      <Footer />
    </main>
  );
});
src/components/footer.tsx
import { component$ } from '@builder.io/qwik';
 
// Импорт загрузчика из макета
import { useProductData } from '../routes/layout.tsx';
 
export const Footer = component$(() => {
  // Использование данных загрузчика
  const signal = useProductData();
  return <footer>Наименование товара: {signal.value.product.name}</footer>;
});

Приведённый выше пример показывает использование useProductData() в двух разных компонентах и в разных файлах. Это намеренное поведение.

src/routes/admin/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useLoginStatus = routeLoader$(async ({ cookie }) => {
  return {
    isUserLoggedIn: checkCookie(cookie),
  };
});
 
export const useCurrentUser = routeLoader$(async ({ cookie }) => {
  return {
    user: currentUserFromCookie(cookie),
  };
});
 
export default component$(() => {
  const loginStatus = useLoginStatus();
  const currentUser = useCurrentUser();
  return (
    <section>
      <h1>Администратор</h1>
      {loginStatus.value.isUserLoggedIn ? (
        <p>Привет, {currentUser.value.user.name}</p>
      ) : (
        <p>Вы не авторизованы</p>
      )}
    </section>
  );
});

Приведённый выше пример показывает использование двух routeLoader$-ов в одном файле. Общий загрузчик useLoginStatus используется для проверки того, вошёл ли пользователь в систему, а более специфический загрузчик useCurrentUser используется для получения данных пользователя.

RequestEvent

Подобно промежуточному ПО или конечной точке, onRequest и onGet, routeLoader$ имеет доступ к API RequestEvent, который содержит информацию о текущем HTTP-запросе.

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

src/routes/product/[user]/index.tsx
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductRecommendations = routeLoader$(async (requestEvent) => {
  console.log('Request headers:', requestEvent.request.headers);
  console.log('Request cookies:', requestEvent.cookie);
  console.log('Request url:', requestEvent.url);
  console.log('Request method:', requestEvent.method);
  console.log('Request params:', requestEvent.params);
 
  // Используйте параметры запроса для получения персонализированных данных
  const res = fetch(`https://.../recommendations?user=${requestEvent.params.user}`);
  const recommendedProducts = (await res.json()) as Product[];
 
  return recommendedProducts;
});

Доступ к данным routeLoader$ из другого routeLoader$.

Вы можете получить доступ к данным одного routeLoader$ из другого routeLoader$, используя метод requestEvent.resolveValue.

src/routes/product/[productId]/index.tsx
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductDetails = routeLoader$(async (requestEvent) => {
  const res = await fetch(`https://.../products/${requestEvent.params.productId}`);
  const product = await res.json();
  return product;
});
 
export const useProductRecommendations = routeLoader$(async (requestEvent) => {
  // Получение информации о товаре из другого загрузчика.
  const product = await requestEvent.resolveValue(useProductDetails);
 
  // Используем полученные данные о товаре для получения персонализированных данных.
  const res = fetch(`https://.../recommendations?product=${product.id}`);
  const recommendedProducts = (await res.json()) as Product[];
 
  return recommendedProducts;
});

Тот же API можно использовать для доступа к данным из routeAction$ или globalAction$.

Неудачная загрузка данных с routeLoader$.

routeLoader$ может использовать метод fail для возврата значения failed, которое является специальным значением, указывающим на то, что загрузчику не удалось загрузить ожидаемые данные.

Кроме того, функция fail позволяет routeLoader$ переопределять код статуса HTTP, например, возвращать 404.

Это полезно, когда загрузчику нужно вернуть значение "ошибки", которое не является undefined, но должно указывать на то, что данные не удалось загрузить.

src/routes/product/[productId]/index.tsx
import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductDetails = routeLoader$(async (requestEvent) => {
  const product = await db.from('products').filter('id', 'eq', requestEvent.params.productId);
  if (!product) {
    // Возвращает значение, указывающее на то, что товар не был найден.
    return requestEvent.fail(404, {
      errorMessage: 'Товар не найден'
    });
  }
  return {
    productName: product.name
  };
});
 
export default component$(() => {
  const product = useProductDetails();
 
  if (product.value.errorMessage) {
    // Отображение неудачной загрузки данных.
    return <div>{product.value.errorMessage}</div>;
  }
  return <div>Наименование товара: {product.value.productName}</div>;
});

Обработка относительных URL-адресов в загрузчиках

В среде выполнения на стороне сервера крайне важно преобразовать относительные URL-адреса в абсолютные URL-адреса для обеспечения надлежащей функциональности. Этого можно добиться, добавив к относительному URL-адресу префикс origin из функции useLocation().

Сформировать абсолютный URL
import { component$ } from '@builder.io/qwik';
import { useLocation } from '@builder.io/qwik-city';
 
export default component$(() => {
  const location = useLocation();
  const relativeUrl = '/mock-data';
  const absoluteUrl = location.url.origin + relativeUrl;
 
  return (
    <section>
      <div>Относительный URL: {relativeUrl}</div>
      <div>Абсолютный URL: {absoluteUrl}</div>
    </section>
  );
});

Соображения по производительности

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

Загрузчики выполняются после обработчиков маршрутов Qwik (onRequest, onGet, onPost и т.д.), и перед рендером компонентов Qwik. Это позволяет загрузчикам начать получение данных как можно быстрее, уменьшая задержку.

Участники

Спасибо всем участникам, которые помогли сделать эту документацию лучше!

  • manucorporat
  • mhevery
  • wtlin1228
  • AnthonyPAlicea
  • the-r3aper7
  • hamatoyogi
  • steve8708
  • iamyuu
  • n8sabes
  • mrhoodz
  • mjschwanitz
  • adamdbradley
  • gioboa