Irbisa · Шпаргалка

Роутинг

React Router v7, вложенные маршруты и layout, params и search params, loaders и actions, защищённые маршруты, разбиение кода по маршрутам

Middle-разработчик · 21 вопрос

  1. 01

    Что такое клиентский роутинг и чем он отличается от обычной серверной навигации?

    При клиентском роутинге приложение перехватывает клики по ссылкам, меняет URL через History API (history.pushState) и рендерит подходящие компоненты без полной перезагрузки страницы.

  2. 02

    Как настроить роутинг в React Router v7 и какие у него есть три режима?

    React Router v7 работает в декларативном режиме (<BrowserRouter> с <Routes>), в data-режиме (createBrowserRouter и RouterProvider, которые добавляют лоадеры, экшены и состояния ожидания) или во фреймворк-режиме (бывший Remix: Vite-плагин, роуты из файлов, SSR и сгенерированные типы).

    import { createBrowserRouter, RouterProvider } from 'react-router';import { createRoot } from 'react-dom/client'; const router = createBrowserRouter([  {    path: '/',…
  3. 03

    Чем отличаются <Link>, <NavLink> и useNavigate и почему не стоит использовать обычный <a href>?

    <Link> рендерит <a>, который выполняет навигацию на клиенте, <NavLink> представляет собой <Link>, знающий, активен ли он (для меню и вкладок), а useNavigate переходит из кода, например после сабмита.

    import { Link, NavLink, useNavigate } from 'react-router'; export function Nav() {  return (    <nav>      <Link to="/">Home</Link>…
  4. 04

    Как в React Router работают вложенные роуты и лейауты и что делает <Outlet>?

    Дочерние роуты вкладываются в родительский, а родитель рендерит <Outlet /> там, где должен появиться совпавший потомок.

    const router = createBrowserRouter([  {    path: '/projects/:projectId',    Component: ProjectLayout,    children: [      { index: true, Component: Overview }, // /projects/7…
  5. 05

    Как в React Router объявить и прочитать динамические параметры URL?

    Отметьте сегмент двоеточием в пути (users/:userId) и читайте его через useParams() в компоненте или из params в лоадере.

    import { useParams } from 'react-router'; // route: { path: 'users/:userId', Component: UserPage }export function UserPage() {  const { userId } = useParams<{ userId: string }>();  const id = Number(userId);…
  6. 06

    Чем отличаются browser-, hash- и memory-роутеры и почему обновлённая глубокая ссылка на статическом хостинге отдаёт 404?

    createBrowserRouter использует настоящие пути через History API, createHashRouter держит маршрут после #, и сервер его не видит, а createMemoryRouter хранит историю в памяти для тестов и виджетов.

  7. 07

    Как читать и обновлять search params через useSearchParams?

    useSearchParams() возвращает URLSearchParams для текущей строки запроса и сеттер, который выполняет навигацию на новую строку.

    import { useSearchParams } from 'react-router'; export function ProductSearch() {  const [params, setParams] = useSearchParams();  const q = params.get('q') ?? '';  const page = Math.max(1, Number(params.get('page')) || 1);…
  8. 08

    Найдите баг: выбор сортировки стирает из URL поисковый запрос и номер страницы.

    function SortSelect() {  const [params, setParams] = useSearchParams();  return (    <select      value={params.get('sort') ?? 'new'}      onChange={(e) => setParams({ sort: e.target.value })}    >      <option value="new">Newest</option>      <option value="price">Price</option>    </select>  );}

    setParams({ sort }) заменяет всю строку запроса этим объектом, поэтому q и page пропадают.

  9. 09

    Что такое лоадеры в React Router и чем они лучше загрузки данных в useEffect?

    Лоадер представляет собой функцию роута, которую роутер вызывает с params и request до рендера этого роута; компонент читает результат через useLoaderData().

    import { createBrowserRouter, useLoaderData, type LoaderFunctionArgs } from 'react-router'; async function userLoader({ params, request }: LoaderFunctionArgs) {  const res = await fetch(`/api/users/${params.userId}`, { signal: request.signal });  if (!res.ok) throw new Response('Not found', { status: 404 });  return (await res.json()) as User;…
  10. 10

    Что такое экшены и <Form> в React Router и что происходит после отправки формы?

    Экшен представляет собой функцию роута, которая обрабатывает не-GET отправку: <Form method="post"> сериализует поля, роутер вызывает action роута с request, а когда он завершится, все лоадеры на странице ревалидируются, и UI показывает свежие данные без ручного перезапроса.

    import { Form, redirect, useActionData, useNavigation, type ActionFunctionArgs } from 'react-router'; export async function action({ request }: ActionFunctionArgs) {  const form = await request.formData();  const title = String(form.get('title') ?? '').trim();  if (!title) return { error: 'Title is required' };…
  11. 11

    Найдите баг: при переходе с /users/1/edit на /users/2/edit URL меняется, но форма по-прежнему показывает имя и email пользователя 1.

    // route: { path: 'users/:userId/edit', loader: userLoader, Component: EditUser }export function EditUser() {  const user = useLoaderData<typeof userLoader>();  const [name, setName] = useState(user.name);  const [email, setEmail] = useState(user.email);   return (    <form>      <input value={name} onChange={(e) => setName(e.target.value)} />      <input value={email} onChange={(e) => setEmail(e.target.value)} />    </form>  );}

    Оба URL совпадают с одним и тем же маршрутом, поэтому React Router оставляет EditUser смонтированным и лишь передаёт ему новые данные лоадера.

  12. 12

    Как реализовать защищённые роуты в React Router?

    Оберните приватные роуты в layout-роут, который проверяет сессию и либо рендерит <Outlet />, либо перенаправляет на страницу входа, запомнив, куда шёл пользователь.

    import { createBrowserRouter, redirect, type LoaderFunctionArgs } from 'react-router'; async function requireUser({ request }: LoaderFunctionArgs) {  const user = await getSession();  if (!user) {    const next = new URL(request.url).pathname;…
  13. 13

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

    export function RequireAuth() {  const { user } = useSession();  const navigate = useNavigate();   useEffect(() => {    if (!user) navigate('/login');  }, [user, navigate]);   return <Outlet />;}

    Guard всегда рендерит <Outlet /> и делает редирект только в эффекте, который выполняется уже после того, как приватная страница закоммичена и отрисована.

  14. 14

    Как предупредить пользователя перед уходом со страницы с несохранёнными изменениями в React Router?

    Нужны два механизма для двух видов ухода: useBlocker останавливает навигацию внутри приложения (ссылки, navigate, «Назад» и «Вперёд») и позволяет показать собственный диалог; обработчик beforeunload покрывает перезагрузку, закрытие вкладки и ввод другого адреса, где браузер показывает свой диалог подтверждения.

    import { useEffect, useState } from 'react';import { useBlocker } from 'react-router'; export function useUnsavedChangesGuard(isDirty: boolean) {  // in-app navigation: links, navigate(), Back and Forward  const blocker = useBlocker(…
  15. 15

    Как разделить код по роутам в React Router?

    Загружайте код роута, только когда его посещают: в data-режиме используйте свойство роута lazy, которое импортирует компонент, лоадер и экшен при навигации; в декларативном режиме оборачивайте компоненты React.lazy в <Suspense>.

    import { createBrowserRouter } from 'react-router'; export const router = createBrowserRouter([  { path: '/', Component: Home }, // stays in the main bundle  {    path: 'reports/:id',…
  16. 16

    Как обрабатывать ошибки и страницы 404 в React Router?

    Задайте роутам ErrorBoundary (или errorElement): всё, что бросили в лоадере, экшене или рендере роута, покажет его вместо роута внутри родительского лейаута.

    import { data, isRouteErrorResponse, useRouteError, type LoaderFunctionArgs } from 'react-router'; export async function loader({ params }: LoaderFunctionArgs) {  const post = await getPost(params.slug!);  if (!post) throw data('Post not found', { status: 404 });  return post;…
  17. 17

    Как показать состояние ожидания при навигации и не дать медленному лоадеру заблокировать страницу?

    useNavigation() отдаёт глобальное состояние (idle, loading, submitting) для прогресс-бара.

    import { Suspense } from 'react';import { Await, useLoaderData, useNavigation, type LoaderFunctionArgs } from 'react-router'; export async function loader({ params }: LoaderFunctionArgs) {  const comments = getComments(params.id!); // not awaited: streamed later  const post = await getPost(params.id!); // awaited: the navigation waits for it…
  18. 18

    Почему после клиентской навигации новая страница иногда открывается прокрученной до середины, и как это исправляет <ScrollRestoration>?

    При клиентском роутинге документ не меняется, поэтому окно сохраняет позицию прокрутки предыдущей страницы, а при «Назад» браузер восстанавливает старое смещение раньше, чем отрендерились данные.

    import { Link, Outlet, ScrollRestoration } from 'react-router'; export function Root() {  return (    <>      <Header />…
  19. 19

    Что изменилось в React Router v7 по сравнению с v6 и как мигрировать?

    Для приложений, включивших все future-флаги v6, переход на v7 не ломает код: пакеты объединены в react-router, Remix стал его фреймворк-режимом, а лоадеры возвращают обычные объекты вместо json().

  20. 20

    Что происходит с выполняющимися лоадерами, когда пользователь снова переходит на другую страницу, и как React Router решает, что ревалидировать?

    React Router оставляет только последнюю навигацию: новый клик прерывает лоадеры предыдущей через request.signal, и устаревшие ответы не попадают в UI.

    import type { LoaderFunctionArgs, ShouldRevalidateFunctionArgs } from 'react-router'; export async function loader({ params, request }: LoaderFunctionArgs) {  // aborted automatically if the user navigates away before it finishes  const res = await fetch(`/api/projects/${params.id}/sidebar`, { signal: request.signal });  if (!res.ok) throw new Response('Failed to load', { status: res.status });…
  21. 21

    Что добавляет фреймворк-режим React Router и когда выбирать его вместо data-режима или Next.js?

    Фреймворк-режим объединяет React Router и бывший Remix: Vite-плагин с routes.ts, серверные loader и action, SSR, пререндер или сборку SPA, разделение кода и сгенерированные типы роутов.

    // app/routes/product.tsximport type { Route } from './+types/product'; export async function loader({ params }: Route.LoaderArgs) {  // runs only on the server: the database client never ships to the browser  const product = await db.product.findUnique({ where: { id: params.id } });…