Загрузчики: данные с сервера
Загрузчик (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, сериализуется и отправляется в браузер, где его видно в исходном коде страницы. Отбирайте только поля, которые нужны компоненту.