Подробные примеры для React Router
Для веб-приложения маршрутизация — это механизм отображения всей или частичной страницы на основе предоставленного URL-адреса, параметров или действий пользователя с кнопкой, ссылкой, значком и т. д.
Сам React не включает маршрутизацию. react-router
совместим с последними версиями React и является наиболее популярным выбором маршрутизации для приложений React. React Router позволяет перемещаться между представлениями различных компонентов. Он считывает и контролирует URL-адрес браузера, а также поддерживает синхронизацию пользовательского интерфейса с URL-адресом.
React Router 6 был выпущен 3 ноября 2021 года. Давайте подробно рассмотрим, как его использовать.
Терминология навигации
Во-первых, мы рассмотрим некоторые термины, которые часто используются при навигации в браузере.
URL-адрес
URL-адрес (унифицированный указатель ресурса) — это адрес данного уникального ресурса в Интернете. В браузере есть адресная строка, в которую пользователи могут вводить определенный URL-адрес или заполнять ее программно.
Интерфейс URL
используется для анализа, создания, нормализации и кодирования URL-адресов. Он имеет ряд свойств, которые можно читать и изменять.
Создайте URL-адрес:
const url = new URL('https://john:password@www.mycompany.com:8080/users/?id=5#profile');
Вот сгенерированный объект:
{ | |
hash: '#profile', | |
host: 'www.mycompany.com:8080', | |
hostname: 'www.mycompany.com', | |
href: 'https://john:password@www.mycompany.com:8080/users/?id=5#profile', | |
origin: 'https://www.mycompany.com:8080', | |
password: 'password', | |
pathname: '/users/', | |
port: '8080', | |
protocol: 'https:', | |
search: '?id=5', | |
searchParams: URLSearchParams {}, | |
username: 'john', | |
} |
url.searchParams.get('id')
возвращает 5.
Расположение
Местоположение — это объект, который представляет местоположение URL. Он основан на объекте window.location
браузера.
Введите URL-адрес www.google.com
в адресной строке браузера, а window.location
будет установлено следующее значение:
{ | |
hash: '', | |
host: 'www.google.com', | |
hostname: 'www.google.com', | |
href: 'https://www.google.com/', | |
origin: 'https://www.google.com', | |
pathname: '/', | |
port: '', | |
protocol: 'https:', | |
search: '', | |
... | |
} |
Вот определения Path
и Location
в React Router:
export interface Path { | |
/** | |
* A URL pathname, beginning with a /. | |
*/ | |
pathname: Pathname; | |
/** | |
* A URL search string, beginning with a ?. | |
*/ | |
search: Search; | |
/** | |
* A URL fragment identifier, beginning with a #. | |
*/ | |
hash: Hash; | |
} | |
export interface Location extends Path { | |
/** | |
* A value of arbitrary data associated with this location. | |
*/ | |
state: unknown; | |
/** | |
* A unique string associated with this location. May be used to safely store | |
* and retrieve data in some other storage API, like `localStorage`. | |
* | |
* Note: This value is always "default" on the initial location. | |
*/ | |
key: Key; | |
} |
История
Существует два API истории, которые отслеживают историю браузера и управляют ею:
history
API: он отслеживает URL-адреса основного фрейма, которые пользователь посещал на любой вкладке в течение всего срока действия профиля.History
интерфейс: он управляет историей сеансов браузера. то есть он отслеживает навигацию на каждой вкладке.
Поскольку интерфейс History
более осмыслен в современных браузерах, window.history
включает запись для History
. Вот пример window.history
:
{ | |
History { | |
length: 9, | |
scrollRestoration: 'auto', | |
state: {state: null, url: '/', metadata: {…}, XCa: {…}}, | |
[[Prototype]]: History { | |
back: ƒ back() | |
forward: ƒ forward() | |
go: ƒ go() | |
pushState: ƒ pushState() | |
replaceState: ƒ replaceState() | |
... | |
} | |
} | |
} |
Вот определение History
в React Router:
export interface History { | |
/** | |
* The last action that modified the current location. The actions are Action.Pop, Action.Push, | |
* and Action.Replace. The initial value is Action.Pop. This value is mutable. | |
*/ | |
readonly action: Action; | |
/** | |
* The current location. This value is mutable. | |
*/ | |
readonly location: Location; | |
/** | |
* Returns a valid href for the given `to` value that may be used as | |
* the value of an <a href> attribute. | |
* | |
* @param to - The destination URL | |
*/ | |
createHref(to: To): string; | |
/** | |
* Pushes a new location onto the history stack, increasing its length by one. | |
* If there were any entries in the stack after the current one, they are | |
* lost. | |
* | |
* @param to - The new URL | |
* @param state - Data to associate with the new location | |
*/ | |
push(to: To, state?: any): void; | |
/** | |
* Replaces the current location in the history stack with a new one. The | |
* location that was replaced will no longer be available. | |
* | |
* @param to - The new URL | |
* @param state - Data to associate with the new location | |
*/ | |
replace(to: To, state?: any): void; | |
/** | |
* Navigates `n` entries backward/forward in the history stack relative to the | |
* current index. For example, a "back" navigation would use go(-1). | |
* | |
* @param delta - The delta in the stack index | |
*/ | |
go(delta: number): void; | |
/** | |
* Navigates to the previous entry in the stack. Identical to go(-1). | |
* | |
* Warning: if the current location is the first location in the stack, this | |
* will unload the current document. | |
*/ | |
back(): void; | |
/** | |
* Navigates to the next entry in the stack. Identical to go(1). | |
*/ | |
forward(): void; | |
/** | |
* Sets up a listener that will be called whenever the current location | |
* changes. | |
* | |
* @param listener - A function that will be called when the location changes | |
* @returns unlisten - A function that may be used to stop listening | |
*/ | |
listen(listener: Listener): () => void; | |
/** | |
* Prevents the current location from changing and sets up a listener that | |
* will be called instead. | |
* | |
* @param blocker - A function that will be called when a transition is blocked | |
* @returns unblock - A function that may be used to stop blocking | |
* | |
*/ | |
block(blocker: Blocker): () => void; | |
} |
Настройка рабочей среды
React Router публикуется с 3 пакетами:
react-router
содержит большую часть основных функций React Router, включая алгоритм сопоставления маршрутов и большинство основных компонентов и перехватчиков.react-router-dom
включает все изreact-router
и добавляет несколько специфичных для DOM API, включая<BrowserRouter>
,<HashRouter>
и<Link>
.react-router-native
включает в себя все изreact-router
и добавляет несколько API, характерных для React Native, в том числе<NativeRouter>
и нативную версию<Link>
.
Для веб-приложений все, что нам нужно, это react-router-dom
.
Как всегда, строим среду Create React App:
npx create-react-app react-router cd react-router
Настройте react-router-dom
:
npm i react-router-dom
Кроме того, устанавливается lorem-ipsum
для генерации текста-заполнителя lorem ipsum для страниц. Текст Lorem ipsum обычно используется в качестве заполнителя в издательском деле, графическом дизайне и веб-разработке.
npm i lorem-ipsum
react-router-dom
и lorem-ipsum
становятся частью dependencies
в package.json
.
"dependencies": { | |
"lorem-ipsum": "^2.0.4", | |
"react-router-dom": "^6.2.1" | |
} |
Настройка маршрутов
Router — это компонент верхнего уровня с отслеживанием состояния, который заставляет работать все остальные компоненты навигации и хуки. React Router имеет BrowserRouter
, HashRouter
, StaticRouter
, NativeRouter
и MemoryRouter
. Для веб-приложений обычно используется BrowserRouter
. Приложение должно иметь один <BrowserRouter>
, обертывающий один или несколько <Routes>
.
<Routes>
проверяет все свои children
<Route>
элементы, чтобы найти наилучшее соответствие, и отображает эту часть пользовательского интерфейса.
<Route>
определяется как объект или элемент маршрута. Если это объект, объект имеет форму { path, element }
. Если это элемент Route, компонент имеет форму <Route path element>
. Когда шаблон пути соответствует текущему URL-адресу, отображается реквизит element
.
Подготавливаем несколько страниц для управления в src/Pages.js
:
import { loremIpsum } from 'lorem-ipsum'; | |
const getPage = (index) => ( | |
<> | |
<h3>Page {index}</h3> | |
<div> | |
Page {index} content: {loremIpsum({ count: 5 })} | |
</div> | |
</> | |
); | |
export const PageOne = () => getPage(1); | |
export const PageTwo = () => getPage(2); |
Строки 3–10 определяют функцию getPage
. Он генерирует озаглавленную страницу, где содержимое страницы генерируется loremIpsum()
с 5 случайными предложениями (строка 7).
Функция getPage
используется для генерации PageOne
(строка 12) и PageTwo
(строка 13). Вот так выглядит PageOne
, и PageTwo
похоже.
В src/App.js
создаются два маршрута:
import { BrowserRouter, Routes, Route } from 'react-router-dom'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
return ( | |
<BrowserRouter> | |
<Routes> | |
<Route path="one" element={<PageOne />} /> | |
<Route path="two" element={<PageTwo />} /> | |
</Routes> | |
</BrowserRouter> | |
); | |
} | |
export default App; |
<BrowserRouter>
и <Routes>
используются для определения маршрутизатора (строки 6–11).
В приложении есть два <Route>
. Когда URL-адрес соответствует пути "one"
, приложение показывает PageOne
(строка 8). Когда URL-адрес соответствует пути, "two"
, приложение показывает PageTwo
(строка 9).
Запустите приложение, выполнив команду npm start
.
https://localhost:3000/one
показывает PageOne
.
https://localhost:3000/two
показывает PageTwo
.
Приложение работает для путей "one"
и "two"
. Однако https://localhost:3000
ничего не показывает, как и любые недопустимые URL-адреса, такие как https://localhost:3000/anything
.
Эту проблему можно решить с помощью подстановочного пути (строка 8):
import { BrowserRouter, Routes, Route } from 'react-router-dom'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
return ( | |
<BrowserRouter> | |
<Routes> | |
<Route path="*" element={<PageOne />} /> | |
<Route path="two" element={<PageTwo />} /> | |
</Routes> | |
</BrowserRouter> | |
); | |
} | |
export default App; |
https://localhost:3000/two
показывает PageTwo
. В противном случае отображается PageOne
.
Поскольку React Router 6 достаточно умен, чтобы выбрать наиболее точное совпадение, порядок маршрута не имеет значения.
Настройка вложенных маршрутов
Два маршрута в приведенном выше примере работают, как и ожидалось. Однако вводить URL-адрес в адресной строке браузера неудобно. Мы хотели бы иметь возможность навигации, нажав на ссылку, которая является <Link>
.
<Link>
отображает доступный элемент <a>
с реальным href
, указывающим на ресурс, на который он ссылается. Щелчок по ссылке устанавливает URL-адрес и отслеживает историю просмотров.
src/MainPage.js
создается с помощью <Link>
s:
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> | |
); |
Строки 4–13 определяют элемент <nav>
с набором навигационных ссылок.
Строка 7 — это ссылка, указывающая на путь "/one"
. Текст ссылки "Page One"
.
Строка 10 — это ссылка, указывающая на путь "/two"
. Текст ссылки "Page Two"
.
Значения <Link to>
могут относиться к пути маршрута, который их отображает. т. е. путь указан без ведущего /
. Альтернативно, строка 7 может указывать на "one"
, а строка 10 может указывать на "two"
.
Используйте MainPage
в src/App.js
:
import { BrowserRouter, Routes, Route } from 'react-router-dom'; | |
import { MainPage } from './MainPage'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
return ( | |
<BrowserRouter> | |
<Routes> | |
<Route index element={<MainPage />} /> | |
<Route path="*" element={<PageOne />} /> | |
<Route path="two" element={<PageTwo />} /> | |
</Routes> | |
</BrowserRouter> | |
); | |
} | |
export default App; |
Строка 9 — это маршрут index
, который является дочерним маршрутом без пути. Когда URL соответствует "/"
, приложение показывает MainPage
. Вот индексная страница:
Нажав на ссылку Page One
, вы попадете на PageOne
. Нажав на ссылку Page Two
, вы попадете на PageTwo
.
Однако внутри PageOne
или PageTwo
мы не можем использовать ссылки для навигации. Чтобы решить эту проблему, мы создаем компонент <Outlet>
в MainPage
. Выходной компонент отображает следующее совпадение ("one"
для PageTwo
и "two"
для PageTwo
) в пути.
Это src/MainPage.js
с розеткой:
import { Link, Outlet } 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> | |
<hr /> | |
<Outlet /> | |
</> | |
); |
Строка 15 — это элемент <hr>
, который представляет собой горизонтальное правило для разделения контента.
Строка 16 — это компонент <Outlet>
для рендеринга активного дочернего маршрута.
<Outlet>
вызывает вложенные маршруты, где каждый маршрут может иметь дочерние маршруты, занимающие часть URL-адреса. Вложенные маршруты обычно используют относительные ссылки (строка 8 и строка 11).
Вот модифицированный src/App.js
:
import { BrowserRouter, Routes, Route } from 'react-router-dom'; | |
import { MainPage } from './MainPage'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
return ( | |
<BrowserRouter> | |
<Routes> | |
<Route path="/" element={<MainPage />}> | |
<Route index element={<div>No page is selected</div>} /> | |
<Route path="*" element={<PageOne />} /> | |
<Route path="two" element={<PageTwo />} /> | |
</Route> | |
</Routes> | |
</BrowserRouter> | |
); | |
} | |
export default App; |
В строке 9 маршрут верхнего уровня — "/"
, и он отображает компонент MainPage
. Под ним есть три дочерних маршрута:
- Индексный маршрут (строка 10): показывает текст
No page is selected
.
- Маршрут с подстановочными знаками (строка 11): показывает компонент
PageOne
.
- Маршрут
"two"
(строка 12): показывает компонентPageTwo
.
Хук useRoutes
Вместо <Routes>
мы также можем использовать хук useRoutes
, чтобы выполнить то же самое.
Вложенные маршруты в src/App.js
можно переписать с помощью useRoutes
:
import { BrowserRouter, useRoutes } from 'react-router-dom'; | |
import { MainPage } from './MainPage'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
const routes = useRoutes([ | |
{ | |
path: '/', | |
element: <MainPage />, | |
children: [ | |
{ index: true, element: <div>No page is selected</div> }, | |
{ path: '*', element: <PageOne /> }, | |
{ path: 'two', element: <PageTwo /> }, | |
], | |
}, | |
]); | |
return routes; | |
} | |
const AppWrapper = () => { | |
return ( | |
<BrowserRouter> | |
<App /> | |
</BrowserRouter> | |
); | |
}; | |
export default AppWrapper; |
В строках 6–16 мы используем ловушку useRoutes
для создания объекта routes
из структуры данных.
Мы должны создать AppWrapper
, чтобы обернуть routes
внутри <browserRouter>
(строки 20–26). В противном случае мы столкнемся со следующей ошибкой:
Error: useRoutes() may be used only in the context of a component.
использованиеМестоположение
Местоположение — это объект, который представляет местоположение URL. Он основан на объекте window.location
браузера.
Хук useLocation
возвращает объект текущего местоположения.
import {useEffect} from 'react'; | |
import { BrowserRouter, useLocation, useRoutes } from 'react-router-dom'; | |
import { MainPage } from './MainPage'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
const location = useLocation(); | |
useEffect(() => { | |
console.log('Current location is', location); | |
}, [location]); | |
const routes = useRoutes([ | |
{ | |
path: '/', | |
element: <MainPage />, | |
children: [ | |
{ index: true, element: <div>No page is selected</div> }, | |
{ path: '*', element: <PageOne /> }, | |
{ path: 'two', element: <PageTwo /> }, | |
], | |
}, | |
]); | |
return routes; | |
} | |
const AppWrapper = () => { | |
return ( | |
<BrowserRouter> | |
<App /> | |
</BrowserRouter> | |
); | |
}; | |
export default AppWrapper; |
В строке 7 location
генерируется хуком useLocation
.
Строки 8–10 определяют useEffect
, который регистрирует значение местоположения, если оно изменяется.
- Когда URL-адрес равен
https://localhost:3000/
, консоль регистрирует:
Current location is {pathname: '/', search: '', hash: '', state: null, key: 'default'}
- Когда URL-адрес равен
https://localhost:3000/one
, консоль регистрирует:
Current location is {pathname: '/one', search: '', hash: '', state: null, key: 'f2114bru'}
- Когда URL-адрес равен
https://localhost:3000/two
, консоль регистрирует:
Current location is {pathname: '/two', search: '', hash: '', state: null, key: 'if9ngy0q'}
- Когда URL-адрес равен
https://localhost:3000/anything
, консоль регистрирует:
Current location is {pathname: '/anything', search: '', hash: '', state: null, key: 'default'}
Хук useParams
Чтобы сделать пример более интересным, мы вложили больше маршрутов. На MainPage
кроме ссылок на "Page One"
и "Page Two"
есть более подробные ссылки на "P1"
(пункт 1) и "P2"
(пункт 2).
Вот модифицированный src/App.js
:
import { useEffect } from 'react'; | |
import { | |
BrowserRouter, | |
useLocation, | |
useRoutes, | |
} from 'react-router-dom'; | |
import { MainPage } from './MainPage'; | |
import { PageOne, PageTwo } from './Pages'; | |
function App() { | |
const location = useLocation(); | |
useEffect(() => { | |
console.log('Current location is', location); | |
}, [location]); | |
const routes = useRoutes([ | |
{ | |
path: '/', | |
element: <MainPage />, | |
children: [ | |
{ index: true, element: <div>No page is selected</div> }, | |
{ path: '*', element: <PageOne /> }, | |
{ | |
path: 'one', | |
element: <PageOne />, | |
children: [{ path: ':id', element: <PageOne /> }], | |
}, | |
{ | |
path: 'two', | |
element: <PageTwo />, | |
children: [{ path: ':id', element: <PageTwo /> }], | |
}, | |
], | |
}, | |
]); | |
return routes; | |
} | |
const AppWrapper = () => { | |
return ( | |
<BrowserRouter> | |
<App /> | |
</BrowserRouter> | |
); | |
}; | |
export default AppWrapper; |
Под путем "one"
(строка 23) есть еще один дочерний маршрут с динамическим параметром ":id"
(строка 25).
По пути "two"
(строка 28) есть еще один дочерний маршрут с динамическим параметром ":id"
(строка 30).
Вот модифицированный src/MainPage.js
:
import { Link, Outlet } from 'react-router-dom'; | |
export const MainPage = () => { | |
return ( | |
<> | |
<nav> | |
<ul> | |
<li> | |
<Link to="one">Page One</Link> - <Link to="one/1">P1</Link> | |
, <Link to="one/2">P2</Link> | |
</li> | |
<li> | |
<Link to="two">Page Two</Link> - <Link to="two/1">P1</Link> | |
, <Link to="two/2">P2</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</> | |
); | |
}; |
Рядом со ссылкой "Page One"
добавляются ссылки P1
(путь "one/1"
) и P2 (путь "one/2"
) (строки 9–10).
Рядом со ссылкой "Page Two"
добавляются ссылки P1
(путь "two/1"
) и P2
(путь "two/2"
) (строки 13–14).
Хук useParams
возвращает объект пар ключ/значение динамических параметров из текущего URL. Вот модифицированный src/Pages.js
с использованием хука useParams
:
import { useParams } from 'react-router-dom'; | |
import { loremIpsum } from 'lorem-ipsum'; | |
const BuildPage = ({index}) => { | |
const { id } = useParams(); | |
return ( | |
<> | |
<h3>Page {index}</h3> | |
<div> | |
Page {index} {id && <span> - paragraph {id}</span>} content: {loremIpsum({ count: 5 })} | |
</div> | |
</> | |
); | |
}; | |
export const PageOne = () => BuildPage({index: 1}); | |
export const PageTwo = () => BuildPage({index: 2}); |
Поскольку хук должен использоваться в компоненте React, строки 4–14 заменяют функцию getPage
на компонент BuildPage
.
Строка 5 использует хук useParams
для получения динамического пути id
. Значение id
отображается в начале содержимого (строка 10).
Строка 14 генерирует PageOne
через BuildPage
.
Строка 15 генерирует PageTwo
через BuildPage
.
Когда URL-адрес равен https://localhost:3000/one/1
, консоль регистрирует:
Current location is {pathname: '/one/1', search: '', hash: '', state: null, key: 'qpdrrtyg'}
useNavigate
Хук useNavigate
возвращает функцию, которую можно использовать для программной навигации. Замените два <Link>
s на <button>
s в src/MainPage.js
:
import { useNavigate, Link, Outlet } from 'react-router-dom'; | |
export const MainPage = () => { | |
const navigate = useNavigate(); | |
return ( | |
<> | |
<nav> | |
<ul> | |
<li> | |
<button onClick={() => navigate('one', { replace: false })}> | |
Page One | |
</button>{' '} | |
- <Link to="one/1">P1</Link>, <Link to="one/2">P2</Link> | |
</li> | |
<li> | |
<button onClick={() => navigate('two', { replace: false })}> | |
Page Two | |
</button>{' '} | |
- <Link to="two/1">P1</Link>, <Link to="two/2">P2</Link> | |
</li> | |
</ul> | |
</nav> | |
<hr /> | |
<Outlet /> | |
</> | |
); | |
}; |
Строка 4 возвращает хук useNavigate
.
Строки 10–12 определяют кнопку "Page One"
, где обработчик onClick
перемещается по относительному пути "one"
.
Строки 16–18 определяют кнопку "Page Two"
, где обработчик onClick
перемещается по относительному пути "two"
.
Запустите приложение. Кнопки действуют аналогично ссылкам.
Функция navigate
имеет две подписи:
- Передайте значение
To
(того же типа, что и<Link to>
) с необязательным вторым аргументом,{ replace, state }
. - Передайте дельта-число, чтобы войти в стек истории. Например,
navigate(-1)
эквивалентно нажатию кнопки «Назад».
Другие крючки
Мы показали, как использовать useLocation
, useNavigate
, useParams
и useRoutes
.
Есть и другие хуки React Router:
useHref
: возвращает URL-адрес, который можно использовать для ссылки на указанноеto
местоположение даже за пределами React Router.useLinkClickHandler
: возвращает обработчик события клика для навигации при создании пользовательской ссылки.useInRouterContext
: возвращаетtrue
, если компонент визуализируется в контексте<Router>
,false
в противном случае.useNavigationType
: возвращает способ перехода пользователя на текущую страницу черезAction.Pop
,Action.Push
илиAction.Replace
в стеке истории.useMatch
: возвращает данные соответствия о маршруте по заданному пути относительно текущего местоположения.useOutlet
: возвращает элемент дочернего маршрута на этом уровне иерархии маршрутов.useResolvedPath
: Сопоставляетpathname
местоположения в заданном значенииto
с путем к текущему местоположению.useSearchParams
: используется для чтения и изменения строки запроса в URL-адресе для текущего местоположения.
Заключение
React Router 6 многофункционален и прост в использовании. Он совместим с последними версиями React. Поскольку он содержит несколько критических изменений по сравнению с версией 5, рекомендуется дождаться выпуска пакета обратной совместимости, прежде чем обновлять приложения.
Спасибо за прочтение. Я надеюсь, что это было полезно. Если вам интересно, ознакомьтесь с другими моими статьями на Medium.
Примечание. Спасибо Уриану Чангу и Джошу Брауну за то, что вместе со мной оценили React Router 6.