React Router — решение для переключения и маршрутизации страниц React. Библиотека появилась еще в 2014 году и стала одним из первых популярных проектов с открытым исходным кодом на основе React. Рассказываем про ключевую концепцию библиотеки и разбираем, почему разработчики выбирают именно ее для организации маршрутизации. Бонусом — напишем небольшое приложение c использованием хуков useLocation
и useNavigate
, и увидим, как на практике работает маршрутизация через React Router.
- Декларативная маршрутизация
- Подготовка окружения
- Настройка маршрутов
- Настройка вложенных маршрутов в React Router v6
- Хук useLocation
- Хук useNavigate
- Другие хуки
Этот подробный гайд больше подходит для тех, кто уже немного знает основы React. Если вы еще не разобрались с ним, советуем перед прочтением статьи пройти большой курс по React на Хекслете.
Декларативная маршрутизация
Декларативная маршрутизация — это стиль кодирования, используемый в React и React Router. Декларативные маршруты React являются компонентами и используют ту же инфраструктуру, что и любое приложение React. Эти маршруты связывают веб-адреса с определенными страницами и другими компонентами, используя мощный механизм рендеринга React и условную логику для программного включения и выключения маршрутов.
Конечно, любой маршрутизатор хорош настолько, насколько хороша его библиотека. React Router в шестой версии предоставляет множество мощных функций для упрощения задач маршрутизации и является предпочтительным решением для маршрутизации React.
Что делает React Router лучшим по сравнению с другими библиотеками маршрутизации:
- Декларативное определение маршрута (с использованием JSX внутри компонентов React)
- Отраслевой стандарт
- Библиотека предлагает множество примеров кода и обширное онлайн-руководство
- React Router предоставляет возможность использования хуков и функциональных компонентов.
Текущая версия React Router v6 внесла ряд ключевых изменений по сравнению с предыдущей версией v5:
- Компонент
<Switch>
был переименован в<Routes>
- Хук
useRoutes()
заменяетreact-router-config
для определения маршрутов как простых объектов - Каждый дочерний компонент
<Routes>
должен быть<Route>
, что может нарушить некоторые предыдущие методы организации и составления маршрутов.
В нашей статье все примеры будут построены c использованием React Router именно шестой версии.
Подготовка окружения
Прежде чем мы приступим к практике, необходимо подготовить рабочее окружение.
Для создания веб-приложений нам нужен react-router-dom
, который включает в себя все, что есть в react-router
, и добавляет несколько специфичных для DOM API компонентов, включая <BrowserRouter>
и <Link>
.
Создадим проект с помощью Create React App
и установим react-router-dom
:
npx create-react-app my-app
cd my-app
npm i react-router-dom
Кроме того, установим lorem-ipsum для генерации текста-заполнителя lorem ipsum
для страниц.
npm i lorem-ipsum
Теперь пакеты react-router-dom
и lorem-ipsum
можно увидеть в package.json
в качестве зависимостей.
Настройка маршрутов
Router — это компонент верхнего уровня с отслеживанием состояния, который заставляет работать все остальные компоненты навигации и хуки. В React Router есть BrowserRouter
, HashRouter
, StaticRouter
, NativeRouter
и MemoryRouter
. Для веб-приложений обычно используется BrowserRouter
. Приложение должно иметь один <BrowserRouter>
, который обертывает один или несколько <Routes>
.
<Routes>
проверяет все свои дочерние элементы <Route>
, чтобы найти наилучшее соответствие, и отображает эту часть пользовательского интерфейса.
<Route>
можно определить либо как объект, либо элемент Route. Если это объект, он имеет форму { path, element }
. Если это элемент Route, компонент имеет вид <Route path element>
. Когда указанный путь path
соответствует текущему URL-адресу, то отображается компонент, указанный в качестве элемента element
. В нашем приложении мы будем использовать именно element
.
Подготовим несколько страниц, для которых будем настраивать маршрутизацию:
// src/Components/Pages.jsx
import { loremIpsum } from 'lorem-ipsum';
const BuildPage = (index) => (
<>
<h3>Page {index}</h3>
<div>
Page {index} content: { loremIpsum({ count: 5 })}
</div>
</>
);
export const PageOne = () => BuildPage(1);
export const PageTwo = () => BuildPage(2);
В src/App.js
создадим два маршрута:
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PageOne, PageTwo } from './Components/Pages';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="one" element={<PageOne />} />
<Route path="two" element={<PageTwo />} />
</Routes>
</BrowserRouter>
);
}
export default App;
В этом коде <BrowserRouter>
и <Routes>
используются для определения маршрутизатора.
В приложении есть два <Route>
. Когда URL-адрес соответствует пути one
, приложение показывает компонент PageOne
. Когда URL-адрес соответствует пути two
, приложение показывает компонент PageTwo
.
Запустим приложение, выполнив команду npm start
.
http://localhost:3000/one
показывает PageOne
http://localhost:3000/two
показывает PageTwo
Приложение работает для путей one
и two
. Однако http://localhost:3000
ничего не показывает, как и любые недействительные URL-адреса, такие как http://localhost:3000/anything
.
Эту проблему можно решить с помощью подстановочного пути:
// App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PageOne, PageTwo } from './Components/Pages';
function App() {
return (
<BrowserRouter>
<Routes>
{/* подстановочный путь */}
<Route path="*" element={<PageOne />} />
<Route path="two" element={<PageTwo />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Теперь http://localhost:3000/two
показывает PageTwo
. Во всех остальных случаях будет отображаться PageOne
. Порядок указания маршрутов не имеет значения, так как React Router 6 выбирает в первую очередь наиболее точное совпадение.
Читайте также: Наталия Давыдова, фронтенд-разработчица в «Точке»: как мое комьюнити помогает джунам найти работу
Настройка вложенных маршрутов в React Router v6
Два маршрута в приведенном выше примере работают, как мы и ожидали. Однако вводить URL-адрес в адресной строке браузера неудобно. Мы хотели бы иметь возможность навигации по ссылке, которая называется <Link>
.
<Link>
отображает доступный элемент <a>
с реальным href
, указывающим на ресурс, на который он ссылается. Клик по ссылке устанавливает URL-адрес и отслеживает историю просмотров.
Создадим главную страницу, которая будет содержать ссылки <Link>
на соответствующие страницы src/MainPage.js
.
// src/MainPage.js
import { Link } from 'react-router-dom';
export const MainPage = () => (
<nav>
<ul>
<li>
<Link to="/one">Page One</Link>
</li>
<li>
<Link to="/two">Page Two</Link>
</li>
</ul>
</nav>
);
Добавим главную страницу в наше приложение и отметим эту страницу как индексный маршрут, который является дочерним маршрутом без пути.
import {
BrowserRouter,
Routes,
Route,
} from 'react-router-dom';
import { PageOne, PageTwo } from './Components/Pages';
import MainPage from './Components/MainPage';
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<MainPage/> } />
<Route path="one" element={<PageOne />} />
<Route path="two" element={<PageTwo />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Теперь, когда URL-адрес соответствует «/»
, приложение будет показывать MainPage: MainPage
.
Нажав на ссылку Page One
, мы перейдем на PageOne
. Нажав на ссылку Page Two
, мы перейдем на PageTwo
.
Однако внутри PageOne
или PageTwo
мы не можем использовать ссылки для навигации. Чтобы решить эту проблему, создадим компонент <Outlet>
в MainPage
. Он позволяет отображать вложенный пользовательский интерфейс при отображении дочерних маршрутов. Таким образом, при клике на 'one'
будет отображаться компонент PageOne
, а при клике на 'two'
— PageTwo
.
Это src/MainPage.js
с Outlet
:
import React from 'react';
import { Link, Outlet } from 'react-router-dom';
const MainPage = () => {
return (
<>
<nav>
<ul>
<li>
<Link to='/one'>Page One</Link>
</li>
<li>
<Link to='/two'>Page Two</Link>
</li>
</ul>
</nav>
<hr />
<Outlet />
</>
)
};
<Outlet>
вызывает вложенные маршруты, где у каждого могут быть свои дочерние маршруты, занимающие часть URL-адреса. Вложенные маршруты обычно описывают через относительные ссылки.
Вот модифицированный src/App.jsx
:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { PageOne, PageTwo } from './Components/Pages';
import MainPage from './Components/MainPage';
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<MainPage />} >
<Route index element={<div>No page is selected.</div> } />
<Route path="one" element={<PageOne />} />
<Route path="two" element={<PageTwo />} />
</Route>
</Routes>
</BrowserRouter>
);
}
http://localhost:3000/
теперь выглядит так:
А так http://localhost:3000/one
:
Хук useLocation
Местоположение — это объект, который представляет местоположение URL. Он основан на объекте браузера window.location
.
Хук useLocation
возвращает объект текущего местоположения.
import React, { useEffect } from 'react';
import { Link, Outlet, useLocation } from 'react-router-dom';
const MainPage = () => {
const location = useLocation();
useEffect(() => {
console.log('Current location is ', location);
}, [location]);
return (
<>
<nav>
<ul>
<li>
<Link to="/one">Page One</Link>
</li>
<li>
<Link to="/two">Page Two</Link>
</li>
</ul>
</nav>
<hr />
<Outlet />
</>
);
};
В переменную location
мы сохраняем местоположение, которое генерируется хуком useLocation
. Внутри хука useEffect
мы будем выводить текущее местоположение при каждом изменении параметра location
.
Если URL-адрес http://localhost:3000/
, то консоль регистрирует:
Current location is {pathname: '/', search: '', hash: '', state: null, key: 'default'}
Если URL-адрес http://localhost:3000/one
, то консоль регистрирует:
Current location is {pathname: '/one', search: '', hash: '', state: null, key: 'f2114bru'}
Когда URL-адрес http://localhost:3000/anything
, то тут консоль регистрирует:
Current location is {pathname: '/anything', search: '', hash: '', state: null, key: 'default'}
Хук useNavigate
Хук useNavigate
возвращает функцию, которую можно использовать для программной навигации. Заменим все <Link>
в нашем приложении на <button>
в src/MainPage.js
:
import React, { useEffect } from 'react';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
const MainPage = () => {
const location = useLocation();
const navigate = useNavigate();
useEffect(() => {
console.log('Current location is ', location);
}, [location]);
return (
<>
<nav>
<ul>
<li>
<button onClick={() => navigate('one', { replace: false })}>
Page One
</button>
</li>
<li>
<button onClick={() => navigate('two', { replace: false })}>
Page Two
</button>
</li>
</ul>
</nav>
<hr />
<Outlet />
</>
)
};
Теперь запустим наше приложение и убедимся, что кнопки работают аналогично ссылкам.
Хук useNavigate
может принимать:
- Либо значение
To
с необязательным вторым аргументом{ replace, state }
, как это работает и в<Link to>
- Либо дельта-число, чтобы войти в стек истории. Например, навигация
(-1)
по своей сути аналогична нажатию кнопки «Назад».
Другие хуки
Мы показали, как использовать useLocation
и useNavigate
. При этом в библиотеке есть и другие хуки — например, useParams
или useRoutes
. С ними можно ознакомиться в официальной документации React Router.
Статья является адаптированным переводом команды Хекслета материалов из следующих источников: 1 и 2.
- Изучите востребованные JavaScript и TypeScript
- Научитесь создавать пользовательские интерфейсы сайтов и приложений
- Освойте самый популярный фреймворк JavaScript — React
- Познакомьтесь с языками веб-разработки HTML и CSS