Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Загрузчики: данные с сервера

Загрузчик (loader) — функция, которую модуль маршрута экспортирует под именем loader. Фреймворк вызывает её на сервере перед отрисовкой страницы, а то, что она вернула, передаёт компоненту. Не нужны ни useEffect, ни состояние «загружается»: страница приходит в браузер уже с данными.

Модуль данных

Пока базы нет, статьи будут храниться в массиве. Создайте файл app/data.server.ts:

// app/data.server.ts
export type Post = { id: number; title: string; body: string };
 
const posts: Post[] = [
  { id: 1, title: "Привет, мир", body: "Первая статья блога." },
  { id: 2, title: "React Router 7", body: "Пишем блог на фреймворке." },
];
 
export async function getPosts() {
  return posts;
}
 
export async function getPost(id: number) {
  return posts.find((post) => post.id === id) ?? null;
}

Окончание .server в имени — сигнал фреймворку: этот файл только для сервера. Если случайно импортировать его в код, который попадает в браузер, сборка остановится с ошибкой. Функции сделаны асинхронными, чтобы позже заменить массив базой, не трогая страницы.

Список статей

Перепишите главную app/routes/home.tsx:

// app/routes/home.tsx
import { Link } from "react-router";
import type { Route } from "./+types/home";
import { getPosts } from "../data.server";
 
export async function loader() {
  const posts = await getPosts();
  return { posts };
}
 
export default function Home({ loaderData }: Route.ComponentProps) {
  return (
    <ul>
      {loaderData.posts.map((post) => (
        <li key={post.id}>
          <Link to={`/posts/${post.id}`}>{post.title}</Link>
        </li>
      ))}
    </ul>
  );
}

На http://localhost:5173/ появится список из двух статей. Тип loaderData выводится из того, что вернул loader, — post.title подсказывается редактором.

Страница статьи

Загрузчик получает объект с params и request (сам HTTP-запрос):

// app/routes/post.tsx
import type { Route } from "./+types/post";
import { getPost } from "../data.server";
 
export async function loader({ params }: Route.LoaderArgs) {
  const post = await getPost(Number(params.postId));
  if (!post) {
    throw new Response("Статья не найдена", { status: 404 });
  }
  return { post };
}
 
export default function Post({ loaderData }: Route.ComponentProps) {
  const { post } = loaderData;
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

/posts/1 покажет первую статью. Для /posts/99 загрузчик выбросит ответ с кодом 404 — как оформить такую страницу, показано в уроке про границы ошибок.

Когда вызывается loader

СитуацияГде выполняется
Первый заход на страницуНа сервере, перед отрисовкой HTML
Переход по LinkНа сервере, браузер получает только данные
После отправки формыНа сервере, данные обновляются сами

Частая ошибка

Возвращать из загрузчика всё подряд — например, целую запись пользователя с хешем пароля. Всё, что вернул loader, сериализуется и отправляется в браузер, где его видно в исходном коде страницы. Отбирайте только поля, которые нужны компоненту.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.