Полное руководство по React Router v6. Часть 1 - Основы React Router
год назад·3 мин. на чтение
React Router — самая популярная библиотека маршрутизации в React, но разобраться в некоторых из более сложных функций может быть немного сложно. В этой серии статей рассмотрим все, что вам нужно знать о React Router, чтобы вы могли с легкостью использовать даже самые продвинутые функции.
Серия статей о React Router v6 состоит из 4 частей.
- Основы React Router (рассматривается в этой статье)
- Продвинутые определения маршрутов
- Управление навигацией
- Подробно о роутерах
Основы React Router
Прежде чем мы начнем углубляться в расширенные функции React Router, сначала поговорим об основах React Router. Чтобы использовать React Router, вам необходимо запуститьnpm i react-router-dom
для установки React Router. Эта библиотека устанавливает DOM версию React Router. Если вы используете React Native, вам нужно будет установить react-router-native
. За исключением этого небольшого отличия, библиотеки работают почти одинаково.
В этой статье сосредоточимся на react-router-dom
, но, как уже упоминалось, обе библиотеки почти идентичны.
Чтобы использовать React Router, вам нужно сделать три вещи.
- Настроить роутер
- Прописать свои маршруты
- Управлять навигацией
Настройка роутера
Настройка роутера является самым простым шагом. Все, что вам нужно сделать, это импортировать конкретный роутер, который вам нужен (BrowserRouter
для веба и NativeRouter
для мобильных устройств) и обернуть все ваше приложение в этот роутер.
Как правило, вы импортируете свой маршрутизатор в файлеimport React from "react" import ReactDOM from "react-dom/client" import App from "./App" import { BrowserRouter } from "react-router-dom" const root = ReactDOM.createRoot(document.getElementById("root")) root.render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> )
index.js
вашего приложения, и он будет оборачивать компонент App
. Роутер работает так же, как контекст в React, и предоставляет всю необходимую информацию вашему приложению, чтобы вы могли выполнять маршрутизацию и использовать все пользовательские хуки из React Router.
Определение маршрутов
Следующим шагом в React Router является определение ваших маршрутов. Обычно это делается на верхнем уровне приложения, например в компонентеApp
, но это можно сделать в любом месте.
Определить маршруты так же просто, как определить компонентimport { Route, Routes } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" export function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> </Routes> ) }
Route
для каждого маршрута в приложении, а затем поместить все эти компоненты Route
в один компонент Routes
. Всякий раз, когда ваш URL-адрес изменяется, React Router будет просматривать маршруты, определенные в вашем компоненте Routes
, и он будет отображать содержимое в пропсе element
роута Route
, который имеет path
, соответствующий URL-адресу. В приведенном выше примере, если бы наш URL-адрес был /books
, то отображался бы компонент BookList
.
Преимущество React Router заключается в том, что при переходе между страницами он обновляет только содержимое внутри вашего компонента Routes
. Весь остальной контент на вашей странице останется прежним, что повысит производительность и удобство использования.
Управление навигацией
Последним шагом к React Router является обработка навигации. Обычно в приложении вы перемещаетесь с помощью тегов<a>
, но React Router использует свой собственный кастомный компонент Link
для обработки навигации. Link
представляет собой просто оболочку вокруг тега <a>
, которая помогает обеспечить правильную обработку всей маршрутизации и условного повторного рендеринга, чтобы вы могли использовать его так же, как обычный тег <a>
.
В нашем примере мы добавили две ссылки на главную страницу и страницу книг. Вы также заметите, что мы использовали пропimport { Route, Routes, Link } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" export function App() { return ( <> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/books">Books</Link></li> </ul> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> </Routes> </> ) }
to
для установки URL-адреса вместо пропса href
, который вы привыкли использовать с тегом <a>
. Это единственное различие между компонентом Link
и тегом <a>
, и это то, что вам нужно помнить, так как легко ошибочно случайно использовать проп href
вместо to
.
Еще одна вещь, которую следует отметить в нашем новом коде, заключается в том, что <nav>
, который мы создаем в верхней части нашей страницы, находится за пределами нашего компонента Routes
, что означает, что при смене страниц этот раздел навигации не будет повторно рендериться, так при изменении URL-адреса изменится только содержимое компонента Routes
.Полное руководство по React Router v6. Часть 2 - Продвинутые определения маршрутов
год назад·8 мин. на чтение
В этой статье рассмотрим то, как описывать роуты в React Router версии 6. Как создавать вложенные маршруты, контекст, хук useRoutes и многое другое.
Серия статей о React Router v6 состоит из 4 частей.
Наш новый код будет работать следующим образом: всякий раз, когда мы сопоставляем маршрут внутри родительского
Контекст
Еще одна важная вещь, которую нужно знать о компонентах
Отдельные
Если вы хотите отобразить два разных раздела контента, которые зависят от URL-адреса приложения, вам потребуется несколько компонентов
Вложенные
Другой способ использования нескольких компонентов
Хук
Последнее, что вам нужно знать об определении маршрутов в React Router, это то, что вы можете использовать объект JavaScript для определения ваших маршрутов вместо JSX.
- Основы React Router
- Продвинутые определения маршрутов (рассматривается в этой статье)
- Управление навигацией
- Подробно о роутерах
- Динамическая маршрутизация
- Приоритет маршрутизации
- Вложенные маршруты
- Множественные маршруты
- Хук
useRoutes
Динамическая маршрутизация
Самая простая и распространенная расширенная функция в React Router — это обработка динамических маршрутов. В нашем примере предположим, что мы хотим визуализировать компонент для отдельных книг в нашем приложении. Мы могли бы жестко закодировать каждый из этих маршрутов, но если у нас есть сотни книг или возможность для пользователей создавать книги, то невозможно жестко закодировать все эти маршруты. Вместо этого нам нужен динамический маршрут.Последний маршрут в приведенном выше примере — это динамический маршрут с динамическим параметром<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> <Route path="/books/:id" element={<Book />} /> </Routes>
:id
. Для определения динамического маршрута в React Router нужно поставить двоеточие перед тем, что должно изменяться. В нашем случае наш динамический маршрут будет соответствовать любому URL-адресу, который начинается с /book
и заканчивается каким-либо значением. Например, /books/1
, /books/bookName
и /books/literally-anything
будут соответствовать нашему динамическому маршруту.
Почти всегда, когда у вас есть такой динамический маршрут, вы хотите получить доступ к динамическому значению в вашем пользовательском компоненте, где на помощь приходит хук useParams
.
Хукimport { useParams } from "react-router-dom" export function Book() { const { id } = useParams() return ( <h1>Book {id}</h1> ) }
useParams
не принимает параметры и возвращает объект с ключами, соответствующими динамическим параметрам в вашем маршруте. В нашем случае нашим динамическим параметром является :id
, поэтому хук useParams
вернет объект, имеющий ключ id
, и значение этого ключа будет фактическим идентификатором в нашем URL. Например, если бы наш URL-адрес был /books/3
, наша страница отображала бы Book 3
.
Приоритет маршрутизации
Когда мы имели дело только с жестко закодированными маршрутами, было довольно легко узнать, какой маршрут будет отображаться, но при работе с динамическими маршрутами это может быть немного сложнее. Возьмем, к примеру, эти маршруты.Если у нас есть URL-адрес<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> <Route path="/books/:id" element={<Book />} /> <Route path="/books/new" element={<NewBook />} /> </Routes>
/books/new
, какой маршрут будет задействован? Технически у нас есть два маршрута, которые совпадают. И /books/:id
, и /books/new
. Они будут совпадать, так как динамический маршрут будет просто предполагать, что new
является частью :id
URL-адреса, поэтому React Router нужен другой способ определить /books/:id
какой маршрут отренедрить.
В старых версиях React Router маршрут, который был определен первым, будет визуализирован, поэтому в нашем случае будет отображаться маршрут /books/:id
, что, очевидно, не то, что нам нужно. К счастью, версия 6 React Router изменила это подход, поэтому теперь React Router будет использовать алгоритм, чтобы определить, какой маршрут, скорее всего, является тем, который вам нужен. В нашем случае мы, очевидно, хотим отобразить /books/new
, поэтому React Router выберет этот маршрут для нас. Фактический принцип работы этого алгоритма очень похож на специфику CSS, поскольку он попытается определить, какой маршрут, соответствующий нашему URL-адресу, является наиболее конкретным (имеет наименьшее количество динамических элементов), и выберет этот маршрут.
Раз уж мы заговорили о приоритете маршрутизации, я также хочу поговорить о том, как создать маршрут, который соответствует всему.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> <Route path="/books/:id" element={<Book />} /> <Route path="/books/new" element={<NewBook />} /> <Route path="*" element={<NotFound />} /> </Routes>
*
соответствует чему угодно, что делает его идеальным для таких вещей, как страница 404
. Маршрут, содержащий *
, также будет менее конкретным, чем все остальное, поэтому вы никогда случайно не сопоставите маршрут *
, когда другой маршрут также совпал бы.
Вложенные маршруты
В приведенном выше примере у нас есть три маршрута, которые начинаются с/books
, поэтому мы можем вложить эти маршруты друг в друга, чтобы очистить наши маршруты.
Это вложение довольно просто сделать. Все, что вам нужно сделать, это создать родительский<Routes> <Route path="/" element={<Home />} /> <Route path="/books"> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes>
Route
, в котором свойство path
будет установлено на общий путь для всех ваших дочерних компонентов Route
. Внутри родительского маршрута вы можете поместить все дочерние компоненты Route
. Единственное отличие состоит в том, что проп path
дочерних компонентов Route
больше не включает общий маршрут /books
. Кроме того, маршрут для /books
заменяется компонентом Route
, который не имеет проп path
, но вместо этого имеет index
. Все это говорит о том, что путь индексного маршрута совпадает с родительским Route
.
Но истинная сила вложенных маршрутов заключается в том, как они обрабатывают общие макеты.
Общие макеты (layout)
Давайте представим, что мы хотим отобразить разделnav
со ссылками на каждую книгу, а также форму для добавления книг с любой из наших страниц. Чтобы сделать это, как правило, нам нужно было бы создать общий компонент для хранения этой навигации, а затем импортировать его в каждый отдельный компонент, связанный с книгой. Однако это создает дублирование, поэтому React Router создал собственный вариант решения этой проблемы. Если вы передадите проп element
родительскому маршруту, он отобразит этот компонент для каждого дочернего Route
, что означает, что вы можете легко разместить общую навигацию или другие общие компоненты на каждой дочерней странице.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BooksLayout />}> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes>
import { Link, Outlet } from "react-router-dom" export function BooksLayout() { return ( <> <nav> <ul> <li><Link to="/books/1">Book 1</Link></li> <li><Link to="/books/2">Book 2</Link></li> <li><Link to="/books/new">New Book</Link></li> </ul> </nav> <Outlet /> </> ) }
Route
/book
, он будет отображать компонент BooksLayout
, который содержит нашу общую навигацию. Route
, какой бы дочерний маршрут ни был сопоставлен, он будет отображаться везде, т.к. компонент Outlet
находится внутри нашего компонента макета. Компонент Outlet
— это, по сути, компонент-заполнитель, который будет отображать любое содержимое нашей текущей страницы. Эта структура невероятно полезна и делает обмен кодом между маршрутами невероятно простым.
Теперь последний способ, которым вы можете поделиться макетами с React Router, - это обернуть дочерние компоненты Route
в родительский Route
, который определяет только проп element
и не имеет пропса path
.
Этот фрагмент кода создаст два маршрута,<Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BooksLayout />}> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> </Route> <Route element={<OtherLayout />}> <Route path="/contact" element={<Contact />} /> <Route path="/about" element={<About />} /> </Route> <Route path="*" element={<NotFound />} /> </Routes>
/contact
и /about
, которые отображаются внутри компонента OtherLayout
. Метод оборачивания нескольких компонентов Route
в родительский компонент Route
без пропса path
полезен, если вы хотите, чтобы эти маршруты использовали один макет, даже если у них нет похожего path
.
Контекст Outlet
Еще одна важная вещь, которую нужно знать о компонентах Outlet
, это то, что они могут принимать проп context
, который будет работать так же, как контекст React.
import { Link, Outlet } from "react-router-dom" export function BooksLayout() { return ( <> <nav> <ul> <li><Link to="/books/1">Book 1</Link></li> <li><Link to="/books/2">Book 2</Link></li> <li><Link to="/books/new">New Book</Link></li> </ul> </nav> <Outlet context={{ hello: "world" }} /> </> ) }
Как видно из этого примера, мы передаем значение контекстаimport { useParams, useOutletContext } from "react-router-dom" export function Book() { const { id } = useParams() const context = useOutletContext() return ( <h1>Book {id} {context.hello}</h1> ) }
{ hello: "world" }
, а затем в нашем дочернем компоненте мы используем хук useOutletContext
для доступа к значению нашего контекста. Это довольно распространенный шаблон для использования, поскольку часто у вас будут общие данные между всеми вашими дочерними компонентами, что является идеальным вариантом использования для этого контекста.
Множественные маршруты
Еще одна невероятно мощная вещь, которую вы можете сделать с помощью React Router, — это использовать несколько компонентовRoutes
одновременно. Это может быть сделано либо в виде двух отдельных компонентов Routes
, либо в виде вложенных Routes
.
Отдельные Routes
Если вы хотите отобразить два разных раздела контента, которые зависят от URL-адреса приложения, вам потребуется несколько компонентов Routes
. Это очень распространенный случай, если, например, у вас есть боковая панель, на которой вы хотите отображать определенный контент для определенных URL-адресов, а также главная страница, которая должна отображать определенный контент на основе URL-адреса.
В приведенном выше примере у нас есть дваimport { Route, Routes, Link } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" import { BookSidebar } from "./BookSidebar" export function App() { return ( <> <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/books">Books</Link></li> </ul> </nav> <aside> <Routes> <Route path="/books" element={<BookSidebar />}> </Routes> </aside> <Routes> <Route path="/" element={<Home />} /> <Route path="/books" element={<BookList />} /> </Routes> </> ) }
Routes
. Основные маршруты Routes
определяют все основные компоненты для нашей страницы, а затем у нас есть вторичные Routes
внутри aside
, которые будут отображать боковую панель для страницы наших книг, когда мы находимся в /books
. Это означает, что если наш URL-адрес /books
, то оба наших компонента Routes
будут отображать контент, поскольку они оба имеют уникальное совпадение для /books
в своих Routes
.
Еще одна вещь, которую вы можете сделать с несколькими компонентами Routes
, — это жестко закодировать проп location
.
Жестко кодируя проп<Routes location="/books"> <Route path="/books" element={<BookSidebar />}> </Routes>
location
, мы переопределяем поведение по умолчанию React Router, поэтому независимо от того, каков URL-адрес нашей страницы, этот компонент Routes
будет соответствовать своему Route
, как если бы URL-адрес был /books
.
Вложенные Routes
Другой способ использования нескольких компонентов Routes
заключается в том, чтобы вложить их друг в друга. Это довольно распространено, если у вас много маршрутов и вы хотите очистить свой код, переместив похожие маршруты в свои собственные файлы.
<Routes> <Route path="/" element={<Home />} /> <Route path="/books/*" element={<BookRoutes />} /> <Route path="*" element={<NotFound />} /> </Routes>
Вложенныеimport { Routes, Route } from "react-router-dom" import { BookList } from "./pages/BookList" import { Book } from "./pages/Book" import { NewBook } from "./pages/NewBook" import { BookLayout } from "./BookLayout" export function BookRoutes() { return ( <Routes> <Route element={<BookLayout />}> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> <Route path="new" element={<NewBook />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> ) }
Routes
в React Router довольно просты в реализации. Все, что вам нужно сделать, это создать новый компонент для хранения вложенных Routes
, этот компонент должен иметь компонент Routes
, а внутри этого компонента Routes
должны быть все компоненты Route
, которые вы сопоставляете с родительским Route
. В нашем случае мы перемещаем все наши маршруты /books
в этот компонент BookRoute
. Затем в родительских Routes
вам нужно определить Route
, который имеет путь, равный пути, разделяемому всеми вашими вложенными Routes
. В нашем случае это будет /books
. Однако важно то, что вам нужно заканчивать path
родительского маршрута маршрутом *
, иначе он не будет правильно соответствовать дочерним роутам.
По сути, код, который мы написали, говорит, что всякий раз, когда маршрут начинается с /book/
, он должен искать внутри компонента BookRoutes
, чтобы увидеть, есть ли соответствующий Route
. Вот почему у нас есть еще один маршрут *
в BookRoutes
, чтобы мы могли гарантировать, что если наш URL-адрес не совпадает ни с одним из BookRoutes
, он правильно отобразит компонент NotFound
.
Хук useRoutes
Последнее, что вам нужно знать об определении маршрутов в React Router, это то, что вы можете использовать объект JavaScript для определения ваших маршрутов вместо JSX.
import { Route, Routes } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" import { Book } from "./Book" export function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/books"> <Route index element={<BookList />} /> <Route path=":id" element={<Book />} /> </Route> </Routes> ) }
Эти два компонента имеют одни и те же маршруты, единственное различие заключается в том, как они были определены. Если вы все же решите, что хотите использовать хукimport { Route, Routes } from "react-router-dom" import { Home } from "./Home" import { BookList } from "./BookList" import { Book } from "./Book" export function App() { const element = useRoutes([ { path: "/", element: <Home /> }, { path: "/books", children: [ { index: true, element: <BookList /> }, { path: ":id", element: <Book /> } ] } ]) return element }
useRoutes
, все пропсы, которые вы обычно передаете компонентам Route, вместо этого просто передаются в виде пар ключ-значение объекта.