Реакт Роутер v6 Руководство для начинающих – CodesCode
Изучите, как навигироваться по приложению React c несколькими представлениями с помощью React Router - стандартной библиотеки маршрутизации для React.
React Router является стандартной библиотекой маршрутизации для React. Когда вам нужно навигировать по приложению React с несколькими видами, вам понадобится маршрутизатор для управления URL-адресами. React Router заботится об этом, поддерживая синхронизацию пользовательского интерфейса приложения и URL-адреса.
В этом руководстве вы познакомитесь с React Router v6 и узнаете о множестве вещей, которые вы можете сделать с его помощью.
- Введение
- Обзор
- Настройка React Router
- Основы React Router
- Вложенная маршрутизация
- Защита маршрутов
- Рабочий демонстрационный пример
- React Router Версия 6.4
- Вывод
- Часто задаваемые вопросы
Введение
React – популярная библиотека JavaScript для создания интерактивных веб-приложений, способных предоставлять динамическое содержимое. Такие приложения могут иметь несколько видов (также известных как страницы), но в отличие от обычных многостраничных приложений, навигация между этими видами не должна приводить к полной перезагрузке страницы. Вместо этого виды отображаются встроенно на текущей странице.
Конечный пользователь, привыкший к многостраничным приложениям, ожидает наличия следующих функций:
- Каждый вид должен иметь URL-адрес, который уникально определяет этот вид. Это позволяет пользователю создавать закладки с URL-адресом для последующего использования, например,
www.example.com/products
. - Кнопки «назад» и «вперед» в браузере должны работать ожидаемым образом.
- Динамически созданные вложенные виды также, если возможно, должны иметь свой собственный URL-адрес, например,
example.com/products/shoes/101
, где 101 – идентификатор продукта.
Маршрутизация – это процесс поддержания синхронизации URL-адреса браузера с тем, что отображается на странице. React Router позволяет вам обрабатывать маршрутизацию декларативно. Декларативный подход к маршрутизации позволяет вам контролировать поток данных в вашем приложении, говоря: «маршрут должен выглядеть так»:
<Route path="/about" element={<About />} />
Вы можете разместить свой компонент <Route>
в любом месте, где вы хотите, чтобы ваш маршрут был отображен. Поскольку <Route>
, <Link>
и все остальные API, с которыми мы будем работать, представляют собой просто компоненты, вы можете легко начать использовать маршрутизацию в React.
Примечание: существует распространенное мнение, что React Router является официальным средством маршрутизации, разработанным Facebook. На самом деле, это сторонняя библиотека, разработанная и поддерживаемая компанией Remix Software.
Обзор
Это руководство разделено на различные разделы. Сначала мы установим React и React Router с помощью npm. Затем мы перейдем непосредственно к некоторым основам. Вы найдете различные демонстрации кода работы с React Router. В этом руководстве рассмотрены следующие примеры:
- базовая навигационная маршрутизация
- вложенная маршрутизация
- вложенная маршрутизация с параметрами пути
- защищенная маршрутизация
Все концепции, связанные с созданием этих маршрутов, будут обсуждаться по ходу дела.
Полный код проекта доступен в этом репозитории на GitHub.
Поехали!
Настройка React Router
Чтобы следовать вместе с этим учебником, вам понадобится последняя версия Node, установленная на вашем компьютере. Если это не так, перейдите на домашнюю страницу Node и загрузите правильные бинарные файлы для вашей системы. В противном случае вы можете рассмотреть возможность использования менеджера версий для установки Node. У нас есть учебник по использованию менеджера версий здесь.
Node поставляется вместе с npm, менеджером пакетов для JavaScript, с помощью которого мы установим некоторые из библиотек, которые мы будем использовать. Больше о использовании npm вы можете узнать здесь.
Вы можете проверить, что они установлены правильно, выполнив следующие команды в командной строке:
node -v> 20.9.0npm -v> 10.1.0
С этим сделано, давайте начнем с создания нового проекта React с помощью инструмента Create React App. Вы можете установить его глобально или использовать npx
, так:
npx create-react-app react-router-demo
Когда это закончится, перейдите в только что созданную директорию:
cd react-router-demo
Библиотека состоит из трех пакетов: react-router, react-router-dom и react-router-native. Основной пакет для маршрутизатора – react-router
, тогда как два других – зависят от среды выполнения. Вы должны использовать react-router-dom
, если вы создаете веб-приложение, и react-router-native
, если вы разрабатываете мобильное приложение с использованием React Native.
Используйте npm для установки пакета react-router-dom
:
npm install react-router-dom
Затем запустите сервер разработки с помощью этой команды:
npm run start
Поздравляю! Теперь у вас есть работающее приложение React с установленным React Router. Вы можете просмотреть запущенное приложение по адресу http://localhost:3000/.
Основы React Router
Теперь давайте ознакомимся с базовым настроем. Для этого мы создадим приложение с тремя отдельными видами: Home, Category и Products.
Компонент Router
Первое, что нам нужно сделать, это обернуть наш компонент <App>
в компонент <Router>
(предоставленный React Router). Существует несколько типов маршрутизаторов, но в нашем случае есть два, которые заслуживают внимания:
Основное различие между ними видно в создаваемых URL:
// <BrowserRouter>https://example.com/about// <HashRouter>https://example.com/#/about
Обычно используется <BrowserRouter>
, поскольку он использует HTML5 History API для синхронизации вашего интерфейса с URL, предлагая более чистую структуру URL без фрагментов хэшей. С другой стороны, <HashRouter>
использует часть URL-адреса, содержащую хэш (window.location.hash
), для управления маршрутизацией, что может быть полезно в средах, где невозможна конфигурация сервера или при поддержке предыдущих версий браузеров, не поддерживающих HTML5 History API. Подробнее о различиях вы можете прочитать здесь.
Обратите также внимание, что несколько новых маршрутизаторов, которые поддерживают различные новые API данных, были представлены в последней версии React Router (v6.4). В этом учебнике мы сосредоточимся на традиционных маршрутизаторах, так как они надежны, хорошо документированы и используются во множестве проектов в Интернете. Однако мы ознакомимся с новинками в v6.4 в следующем разделе.
Итак, давайте импортируем компонент <BrowserRouter>
и обернем его вокруг компонента <App>
. Измените файл index.js
следующим образом:
// src/index.jsimport 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>);
Этот код создает экземпляр объекта history
для нашего компонента <App>
. Давайте разберемся, что это означает.
Немного истории
Библиотека
history
позволяет управлять историей сеанса в любой среде выполнения JavaScript. Объектhistory
абстрагирует различия в различных средах и предоставляет минимальный интерфейс, который позволяет управлять стеком истории, переходить по нему и сохранять состояние между сеансами. — remix-run
Каждый компонент <Router>
создает объект history
, который отслеживает текущее и предыдущие местоположения в стеке. Когда изменяется текущее местоположение, происходит повторный рендеринг представления, и вы получаете ощущение навигации.
Как изменяется текущее местоположение? В React Router v6 хук useNavigate предоставляет функцию navigate
, которая может быть использована для этой цели. Функция navigate
вызывается при нажатии на компонент <Link>
, и ее также можно использовать для замены текущего местоположения, передавая объект параметров с свойством replace: true
.
Другие методы, такие как navigate(-1)
для перехода назад и navigate(1)
для перехода вперед, используются для навигации по стеку истории назад или вперед на одну страницу.
Приложения не нужно создавать собственные объекты history
; эту задачу управляет компонент <Router>
. Вкратце, он создает объект истории, подписывается на изменения в стеке и изменяет свое состояние при изменении URL. Это вызывает повторный рендеринг приложения и обеспечивает правильное отображение пользовательского интерфейса.
Перейдем к ссылкам и маршрутам.
Компоненты Link
и Route
Компонент <Route>
является самым важным компонентом в React Router. Он отображает пользовательский интерфейс, если текущий путь соответствует пути маршрута. Идеально, если у компонента <Route>
есть свойство с именем path
, и если имя пути совпадает с текущим местоположением, он будет отображаться.
Компонент <Link>
, с другой стороны, используется для навигации между страницами. Он сравним с элементом якоря HTML. Однако использование якорных ссылок приведет к полной перезагрузке страницы, чего мы не хотим. Поэтому вместо этого мы можем использовать <Link>
для перехода на определенный URL и получения повторного рендеринга представления без перезагрузки.
Теперь мы рассмотрели все, что вам нужно, чтобы наше приложение работало. Удалите все файлы, кроме index.js
и App.js
из папки src
проекта, затем обновите файл App.js
следующим образом:
import { Link, Route, Routes } from 'react-router-dom';const Home = () => ( <div> <h2>Home</h2> <p>Добро пожаловать на нашу домашнюю страницу!</p> </div>);const Categories = () => ( <div> <h2>Categories</h2> <p>Просмотр товаров по категориям.</p> </div>);const Products = () => ( <div> <h2>Products</h2> <p>Просмотр отдельных товаров.</p> </div>);export default function App() { return ( <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/categories">Categories</Link> </li> <li> <Link to="/products">Products</Link> </li> </ul> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/categories" element={<Categories />} /> <Route path="/products" element={<Products />} /> </Routes> </div> );}
Здесь мы объявили три компонента – <Home>
, <Categories>
и <Products>
, которые представляют разные страницы в нашем приложении. Используются компоненты <Routes>
и <Route>
, импортированные из React Router, для определения логики маршрутизации.
В компоненте <App>
у нас есть простое навигационное меню, где каждый элемент является компонентом <Link>
из React Router. Компоненты <Link>
используются для создания переходов по ссылкам на разные части приложения, каждая из которых связана с определенным путем (/
, /categories
и /products
соответственно). Обратите внимание, что в более крупном приложении это меню можно инкапсулировать внутри компонента макета, чтобы сохранить однородную структуру в разных представлениях. Вы также можете добавить какой-то активный класс (например, используя компонент NavLink) для текущего выбранного элемента меню. Однако, чтобы сосредоточиться на главном, мы здесь пропустим это.
Под навигационным меню компонент <Routes>
используется в качестве контейнера для коллекции отдельных компонентов <Route>
. Каждый компонент <Route>
связан с определенным путем и компонентом React, который будет отображаться, когда путь соответствует текущему URL. Например, когда URL равен /categories
, будет отображаться компонент <Categories>
.
Примечание: в предыдущих версиях React Router, /
соответствовал и /
, и /categories
, что означало, что оба компонента отображались. Решением этой проблемы было бы передать свойство exact
в компонент <Route>
, чтобы только точный путь соответствовал. Это поведение изменилось в версии 6, поэтому теперь все пути соответствуют точно по умолчанию. Как мы увидим в следующем разделе, если вам нужно сопоставить более части URL из-за наличия вложенных маршрутов, используйте знак многоточия *
– например, <Route path="categories/*" ...>
.
Если вы следуете за нашим примером, перед тем, как продолжить, потратьте немного времени, чтобы щелкнуть по приложению и убедиться, что все работает как ожидается.
Вложенная маршрутизация
Топ-уровневые маршруты хороши, но со временем большинству приложений потребуется возможность создавать вложенные маршруты – например, для отображения конкретного продукта или редактирования определенного пользователя.
В React Router v6 маршруты вкладываются, размещая компоненты <Route>
внутри других компонентов <Route>
в коде JSX. Таким образом, вложенные компоненты <Route>
естественным образом отражают вложенную структуру URL, которые они представляют.
Давайте посмотрим, как мы можем реализовать это в нашем приложении. Измените App.js
следующим образом (где ...
указывает на то, что предыдущий код остается неизменным):
import { Link, Route, Routes } from 'react-router-dom';import { Categories, Desktops, Laptops } from './Categories';const Home = () => ( ... );const Products = () => ( ... );export default function App() { return ( <div> <nav>...</nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/categories/" element={<Categories />}> <Route path="desktops" element={<Desktops />} /> <Route path="laptops" element={<Laptops />} /> </Route> <Route path="/products" element={<Products />} /> </Routes> </div> );}
Как видите, мы переместили компонент <Categories>
на отдельную страницу и теперь импортируем два дополнительных компонента, а именно <Desktops>
и <Laptops>
.
Мы также внесли некоторые изменения в компонент <Routes>
, о которых мы рассмотрим в следующем разделе.
Сначала создайте файл Categories.js
в той же папке, где находится ваш файл App.js
. Затем добавьте следующий код:
// src/Categories.js
import { Link, Outlet } from 'react-router-dom';
export const Categories = () => (
<div>
<h2>Категории</h2>
<p>Просматривайте товары по категории.</p>
<nav>
<ul>
<li>
<Link to="desktops">Настольные ПК</Link>
</li>
<li>
<Link to="laptops">Ноутбуки</Link>
</li>
</ul>
</nav>
<Outlet />
</div>
);
export const Desktops = () => <h3>Страница настольных ПК</h3>;
export const Laptops = () => <h3>Страница ноутбуков</h3>;
Обновите свое приложение (это должно происходить автоматически, если сервер разработки запущен), а затем нажмите на ссылку «Категории». Теперь вы должны увидеть два новых пункта меню («Настольные ПК» и «Ноутбуки»), и нажатие на любой из них отобразит новую страницу внутри исходной страницы «Категории».
Итак, что мы только что сделали?
В файле App.js
мы изменили наш маршрут /categories
следующим образом:
<Route path="/categories/" element={<Categories />}>
<Route path="desktops" element={<Desktops />} />
<Route path="laptops" element={<Laptops />} />
</Route>
В обновленном коде компонент <Route>
для /categories
был изменен так, чтобы он включал два вложенных компонента <Route>
внутри него — один для /categories/desktops
и другой для /categories/laptops
. Это изменение показывает, как React Router позволяет компоновать его конфигурацию маршрутизации.
Путем вложения компонентов <Route>
внутри <Route>
для /categories
мы создаем более структурированный URL и иерархию пользовательского интерфейса. Таким образом, когда пользователь переходит на /categories/desktops
или /categories/laptops
, соответствующий компонент <Desktops>
или <Laptops>
будет отображаться внутри компонента <Categories>
, показывая ясную родительскую-дочернюю связь между маршрутами и компонентами.
Примечание: путь вложенного маршрута автоматически формируется путем объединения путей его предков с его собственным путем.
Мы также изменили наш компонент <Categories>
, чтобы он включал <Outlet />
:
export const Categories = () => (
<div>
<h2>Категории</h2>
...
<Outlet />
</div>
);
Элемент <Outlet>
размещается в родительских элементах маршрутов для отображения их дочерних элементов маршрутов. Это позволяет вложенному интерфейсу отображаться, когда отображаются дочерние маршруты.
Этот композиционный подход делает конфигурацию маршрутизации более декларативной и понятной, хорошо сочетающейся с компонентной архитектурой React.
Доступ к свойствам маршрутизатора с помощью хуков
В более ранних версиях, некоторые свойства передавались неявно в компоненте. Например:
const Home = (props) => {
console.log(props);
return (
<h2>Домашняя страница</h2>
);
};
Вышеуказанный код выведет следующее:
{ history: { ... }, location: { ... }, match: { ... }}
В React Router версии 6 подход к передаче свойств маршрутизатора изменился для обеспечения более явного и основанного на хуках метода. Свойства маршрутизатора history
, location
и match
больше не передаются неявно в компонент. Вместо этого предоставляется набор хуков для доступа к этой информации.
Например, для доступа к объекту location
вы будете использовать хук useLocation. Хук useMatch возвращает данные сопоставления о маршруте по заданному пути. Объект history
больше не является явно доступным, вместо этого хук useNavigate вернет функцию, которая позволит вам программно переходить.
Есть еще много других хуков для исследования, и вместо того, чтобы перечислять их все здесь, я бы посоветовал вам ознакомиться с официальной документацией, где доступные хуки можно найти в боковой панели слева.
Далее давайте рассмотрим один из этих хуков более подробно и сделаем наш предыдущий пример более динамичным.
Вложенная динамическая маршрутизация
Для начала измените маршруты в файле App.js
следующим образом:
<Routes> <Route path="/" element={<Home />} /> <Route path="/categories/" element={<Categories />}> <Route path="desktops" element={<Desktops />} /> <Route path="laptops" element={<Laptops />} /> </Route> <Route path="/products/*" element={<Products />} /></Routes>
Наблюдательные фанатики среди вас заметят, что теперь у /products
появилось дополнительное /*
. В React Router версии 6 /*
позволяет указать, что компонент <Products>
может иметь дочерние маршруты, и является заполнителем для любых дополнительных сегментов пути, которые могут следовать за /products
в URL. Таким образом, когда вы переходите по URL, например, /products/laptops
, компонент <Products>
по-прежнему будет сопоставлен и отображен, и он сможет дополнительно обрабатывать часть пути laptops
с помощью своих собственных вложенных элементов <Route>
.
Далее, переместите компонент <Products>
в собственный файл:
// src/App.js...import Products from './Products';const Home = () => ( ... );export default function App() { ... }
Наконец, создайте файл Products.js
и добавьте следующий код:
// src/Products.jsimport { Route, Routes, Link, useParams } from 'react-router-dom';const Item = () => { const { name } = useParams(); return ( <div> <h3>{name}</h3> <p>Детали продукта для {name}</p> </div> );};const Products = () => ( <div> <h2>Продукты</h2> <p>Обзор отдельных продуктов.</p> <nav> <ul> <li> <Link to="dell-optiplex-3900">Dell OptiPlex 3090</Link> </li> <li> <Link to="lenovo-thinkpad-x1">Lenovo ThinkPad X1</Link> </li> </ul> </nav> <Routes> <Route path=":name" element={<Item />} /> </Routes> </div>);export default Products;
Здесь мы добавили <Route>
для компонента <Item>
(объявлен в верхней части страницы). Путь маршрута установлен на :name
, который будет сопоставляться с любым сегментом пути, следующим за родительским маршрутом, и передавать этот сегмент в виде параметра с именем name
в компонент <Item>
.
Внутри компонента <Item>
мы используем хук useParams. Он возвращает объект пар ключ/значение динамических параметров из текущего URL. Если мы выведем это в консоль для маршрута /products/laptops
, увидим:
Object { "*": "laptops", name: "laptops" }
Затем мы можем использовать деструктуризацию объекта, чтобы получить этот параметр напрямую и отобразить его внутри тега <h3>
.
Попробуйте! Как вы увидите, компонент <Item>
перехватывает все ссылки, которые вы объявляете в вашем навигационном меню и создает страницы динамически.
Вы также можете попробовать добавить еще несколько пунктов меню:
<li> <Link to="cyberpowerpc-gamer-xtreme">CyberPowerPC Gamer Xtreme</Link></li>
Наше приложение учтет эти новые страницы.
Этот способ захвата динамических сегментов URL и использования их в качестве параметров внутри наших компонентов позволяет более гибкую маршрутизацию и отображение компонентов на основе структуры URL.
Давайте развивать эту идею в следующем разделе.
Вложенная маршрутизация с параметрами пути
В реальном приложении роутер должен управлять данными и отображать их динамически. Допустим, у нас есть некоторые данные о продуктах, полученные от API в следующем формате:
const productData = [ { id: 1, name: "Dell OptiPlex 3090", description: "Dell OptiPlex 3090 - это компактный настольный компьютер, предлагающий различные возможности для удовлетворения ваших деловых потребностей.", status: "В наличии", }, { id: 2, name: "Lenovo ThinkPad X1 Carbon", description: "С дизайном, отличающимся элегантностью и прочностью, Lenovo ThinkPad X1 Carbon - это высокопроизводительный ноутбук, идеально подходящий для профессионалов в поездках.", status: "Нет в наличии", }, { id: 3, name: "CyberPowerPC Gamer Xtreme", description: "CyberPowerPC Gamer Xtreme - это игровой настольный компьютер с мощным процессором и графическими возможностями для безупречного игрового опыта.", status: "В наличии", }, { id: 4, name: "Apple MacBook Air", description: "Apple MacBook Air - это легкий и компактный ноутбук с высокого разрешения дисплеем Retina и мощными производительными возможностями.", status: "Нет в наличии", },];
Допустим, нам нужны маршруты для следующих путей:
/products
: это должно отображать список продуктов./products/:productId
: если продукт с:productId
существует, то должны отображаться данные о продукте, в противном случае должно отображаться сообщение об ошибке.
Замените текущее содержимое Products.js
на следующее (убедитесь, что скопировали данные о продуктах выше):
import { Link, Route, Routes } from "react-router-dom";import Product from "./Product";const productData = [ ... ];const Products = () => { const linkList = productData.map((product) => { return ( <li key={product.id}> <Link to={`${product.id}`}>{product.name}</Link> </li> ); }); return ( <div> <h3>Products</h3> <p>Browse individual products.</p> <ul>{linkList}</ul> <Routes> <Route path=":productId" element={<Product data={productData} />} /> <Route index element={<p>Please select a product.</p>} /> </Routes> </div> );};export default Products;
Внутри компонента мы создаем список компонентов <Link>
, используя свойство id
от каждого из наших продуктов. Мы сохраняем это в переменной linkList
, перед тем как отображать его на странице.
Далее идут два компонента <Route>
. Первый имеет свойство path
со значением :productId
, которое (как мы видели ранее) является параметром маршрута. Это позволяет захватить и использовать значение из URL в этом сегменте в качестве productId
. Свойство element
этого компонента <Route>
описывает, чтобы отображать компонент <Product>
, передавая ему массив productData
в качестве свойства. Когда URL соответствует шаблону, этот компонент <Product>
будет отображаться, с захваченным productId
из URL.
Второй компонент <Route>
использует индексное свойство, чтобы отобразить текст «Пожалуйста, выберите продукт», когда URL точно соответствует базовому пути. Свойство index
указывает, что этот маршрут является базовым или «индексным» маршрутом в этой настройке <Routes>
. Таким образом, когда URL соответствует базовому пути — т.е. /products
— будет отображаться это сообщение.
Теперь вот код компонента <Product>
, на который мы ссылались выше. Вам нужно создать этот файл по адресу src/Product.js
:
import { useParams } from 'react-router-dom';const Product = ({ data }) => { const { productId } = useParams(); const product = data.find((p) => p.id === Number(productId)); return ( <div> {product ? ( <div> <h3> {product.name} </h3> <p>{product.description}</p> <hr /> <h4>{product.status}</h4> </div> ) : ( <h2>Извините. Продукт не существует.</h2> )} </div> );};export default Product;
Здесь мы используем хук useParams
для доступа к динамическим частям пути URL как пары ключ/значение. Опять же, мы используем деструктуризацию, чтобы получить данные, которые нас интересуют (productId
).
Метод find
используется на массиве data
для поиска и возврата первого элемента, чье свойство id
соответствует полученному из URL параметру productId
.
Теперь, когда вы посетите приложение в браузере и выберите Продукты, вы увидите отображаемое подменю, которое ihrerseits отображает данные о продукте.
Прежде чем продолжить, поиграйтесь с демонстрацией. Убедитесь, что все работает и вы понимаете, что происходит в коде.
Защита маршрутов
Общим требованием для многих современных веб-приложений является обеспечение доступа к определенным частям сайта только для авторизованных пользователей. В этом разделе мы рассмотрим, как реализовать защищенный маршрут, чтобы при попытке получить доступ к /admin
, пользователю потребуется войти в систему.
Однако есть несколько аспектов React Router, о которых нам нужно сначала рассказать.
Программная навигация в React Router v6
В версии 6 программное перенаправление на новое местоположение достигается с помощью хука useNavigate
. Этот хук предоставляет функцию, которая может быть использована для программной навигации на другой маршрут. Она может принимать объект в качестве второго параметра, используемого для указания различных опций. Например:
const navigate = useNavigate();navigate('/login', { state: { from: location }, replace: true});
Это перенаправит пользователя на /login
, передавая значение location
для сохранения его в состоянии истории, которое мы затем можем получить на целевом маршруте с помощью хука useLocation
. Указание replace: true
также заменит текущую запись в стеке истории вместо добавления новой. Это имитирует поведение устаревшего компонента <Redirect>
в версии 5.
Вкратце: если кто-то попытается получить доступ к маршруту /admin
без авторизации, он будет перенаправлен на маршрут /login
. Информация о текущем местоположении передается через свойство state
, так что при успешной аутентификации пользователь может быть перенаправлен обратно на страницу, к которой он пытался получить доступ.
Пользовательские Маршруты
Следующее, на что мы должны обратить внимание, – это пользовательские маршруты. Пользовательский маршрут в React Router – это пользовательское определенное компонент, которое позволяет добавить дополнительный функционал или поведение в процессе маршрутизации. Он может инкапсулировать конкретную логику маршрутизации, такую как проверка аутентификации, и рендерить разные компоненты или выполнять действия на основе определенных условий.
Создайте новый файл PrivateRoute.js
в каталоге src
и добавьте следующее содержимое:
import { useEffect } from 'react';import { useNavigate, useLocation } from 'react-router-dom';import { fakeAuth } from './Login';const PrivateRoute = ({ children }) => { const navigate = useNavigate(); const location = useLocation(); useEffect(() => { if (!fakeAuth.isAuthenticated) { navigate('/login', { state: { from: location }, replace: true, }); } }, [navigate, location]); return fakeAuth.isAuthenticated ? children : null;};export default PrivateRoute;
Здесь происходит несколько вещей. Прежде всего, мы импортируем что-то, называемое fakeAuth
, которое предоставляет свойство isAuthenticated
. Мы рассмотрим это более подробно позже, но пока достаточно знать, что это то, что мы будем использовать для определения статуса входа пользователя.
Компонент принимает свойство children
. Это будет защищенное содержимое, вокруг которого обернут компонент <PrivateRoute>
, когда он вызывается. Например:
<PrivateRoute> <Admin /> <-- children</PrivateRoute>
В теле компонента используются хуки useNavigate
и useLocation
для получения функции navigate
и текущего объекта location
соответственно. Если пользователь не аутентифицирован, как проверено по условию !fakeAuth.isAuthenticated
, вызывается функция navigate
, чтобы перенаправить пользователя на маршрут /login
, как описано в предыдущем разделе.
Выражение возврата компонента снова проверяет статус аутентификации. Если пользователь аутентифицирован, рендерятся его дочерние компоненты. Если пользователь не аутентифицирован, возвращается null
, ничего не рендерится.
Также обратите внимание, что мы оборачиваем вызов функции navigate
в хук useEffect React. Это связано с тем, что вызов функции navigate
не должен производиться непосредственно в теле компонента, так как это вызывает обновление состояния во время рендеринга. Обертывание его в хуке useEffect
гарантирует, что он будет вызываться после рендеринга компонента.
Важное Примечание о Безопасности
В приложении реального мира вам необходимо проверять любой запрос на получение защищенного ресурса на вашем сервере. Позвольте мне сказать это снова…
В приложении реального мира вам необходимо проверять любой запрос на получение защищенного ресурса на вашем сервере.
Это связано с тем, что все, что работает на клиенте, может быть потенциально взломано или подделано. Например, в приведенном выше коде можно просто открыть инструменты разработчика React и изменить значение isAuthenticated
на true
, тем самым получив доступ к защищенной области.
Аутентификация в приложении React заслуживает собственного учебника, но один из способов ее реализации – использование JSON Web Tokens. Например, вы можете иметь конечную точку на вашем сервере, которая принимает комбинацию имени пользователя и пароля. Когда она получает их (через Ajax), она проверяет, являются ли учетные данные действительными. Если да, она отвечает JWT, который сохраняет React-приложение (например, в sessionStorage
), а если нет, она отправляет ответ 401 Unauthorized
обратно клиенту.
Предполагая успешный вход, клиент затем отправляет JWT в заголовке вместе с любым запросом на получение защищенного ресурса. Затем сервер проверяет его на валидность, прежде чем отправить ответ.
При сохранении паролей сервер не будет их хранить в открытом виде. Вместо этого он будет их шифровать — например, с использованием bcryptjs.
Реализация защищенного маршрута
Теперь давайте реализуем наш защищенный маршрут. Измените файл App.js
следующим образом:
import { Link, Route, Routes } from 'react-router-dom';import { Categories, Desktops, Laptops } from './Categories';import Products from './Products';import Login from './Login';import PrivateRoute from './PrivateRoute';const Home = () => ( <div> <h2>Домашняя страница</h2> <p>Добро пожаловать на нашу домашнюю страницу!</p> </div>);const Admin = () => ( <div> <h2>Добро пожаловать админ!</h2> </div>);export default function App() { return ( <div> <nav> <ul> <li> <Link to="/">Домой</Link> </li> <li> <Link to="/categories">Категории</Link> </li> <li> <Link to="/products">Продукты</Link> </li> <li> <Link to="/admin">Админская зона</Link> </li> </ul> </nav> <Routes> <Route path="/" element={<Home />} /> <Route path="/categories/" element={<Categories />}> <Route path="desktops" element={<Desktops />} /> <Route path="laptops" element={<Laptops />} /> </Route> <Route path="/products/*" element={<Products />} /> <Route path="/login" element={<Login />} /> <Route path="/admin" element={ <PrivateRoute> <Admin /> </PrivateRoute> } /> </Routes> </div> );}
Как видите, мы добавили компонент <Admin>
в верхней части файла, и мы включаем наш <PrivateRoute>
внутри компонента <Routes>
. Как упоминалось ранее, этот пользовательский маршрут отображает компонент <Admin>
, если пользователь вошел в систему. В противном случае, пользователь перенаправляется на /login
.
Наконец, создайте файл Login.js
и добавьте следующий код:
// src/Login.jsimport { useState, useEffect } from 'react';import { useNavigate, useLocation } from 'react-router-dom';export default function Login() { const navigate = useNavigate(); const { state } = useLocation(); const from = state?.from || { pathname: '/' }; const [redirectToReferrer, setRedirectToReferrer] = useState(false); const login = () => { fakeAuth.authenticate(() => { setRedirectToReferrer(true); }); }; useEffect(() => { if (redirectToReferrer) { navigate(from.pathname, { replace: true }); } }, [redirectToReferrer, navigate, from.pathname]); return ( <div> <p>Для просмотра страницы {from.pathname} необходимо войти в систему</p> <button onClick={login}>Войти</button> </div> );}/* Функция фальшивой аутентификации */export const fakeAuth = { isAuthenticated: false, authenticate(cb) { this.isAuthenticated = true; setTimeout(cb, 100); },};
В приведенном выше коде мы пытаемся получить значение для URL-адреса, к которому пользователь пытался получить доступ до запроса входа в систему. Если его нет, мы устанавливаем его в { pathname: "/" }
.
Затем мы используем хук useState React для инициализации свойства redirectToReferrer
со значением false
. В зависимости от значения этого свойства, пользователь либо перенаправляется туда, куда он хотел пойти (то есть, пользователь вошел в систему), либо пользователю предлагается кнопка для входа в систему.
После нажатия на кнопку выполняется метод fakeAuth.authenticate
, который устанавливает значение fakeAuth.isAuthenticated
равным true
и (в функции обратного вызова) обновляет значение redirectToReferrer
на true
. Это приводит к перерисовке компонента и перенаправлению пользователя.
Рабочий демо-пример
Давайте соберем все кусочки пазла вместе. Вот окончательное демо-приложение, которое мы создали с использованием React Router.
https://codesandbox.io/embed/react-router-demo-wpwmcv
React Router версии 6.4
Прежде чем мы закончим, стоит упомянуть о выходе React Router v6.4. Несмотря на то, что это выглядит как незначительное обновление, эта версия внесла некоторые революционные новые функции. Например, теперь в него включены API для загрузки данных и мутации из Remix, которые представляют собой совершенно новую парадигму для синхронизации пользовательского интерфейса с вашими данными.
С версии 6.4 вы можете определить функцию loader
для каждого маршрута, которая отвечает за загрузку данных, необходимых для этого маршрута. Внутри вашего компонента вы используете хук useLoaderData
для доступа к данным, загруженным вашей функцией загрузки. Когда пользователь переходит по маршруту, React Router автоматически вызывает соответствующую функцию загрузки, получает данные и передает их компоненту через хук useLoaderData
, без особых усилий. Это способствует созданию паттерна, в котором загрузка данных прямо связана с маршрутизацией.
Также есть новый компонент <Form>
, который предотвращает отправку запроса браузером на сервер и отправляет его вместо этого на action
вашего маршрута. Затем React Router автоматически перезагружает данные на странице после завершения действия, что означает, что все ваши хуки useLoaderData
обновляются, и пользовательский интерфейс автоматически синхронизируется с вашими данными.
Чтобы использовать эти новые API, вам нужно использовать новый компонент <RouterProvider />
. Он принимает свойство router
, которое создается с помощью новой функции createBrowserRouter.
Обсуждение всех этих изменений подробно выходит за рамки данной статьи, но если вам интересно узнать больше, я бы посоветовал следовать за официальным учебником по React Router.
Итог
Как вы увидели в этой статье, React Router – это мощная библиотека, которая дополняет React и позволяет создавать более удобную, декларативную маршрутизацию в ваших приложениях React. На момент написания статьи, текущая версия React Router – v6.18, и библиотека претерпела значительные изменения с версии 5. Это связано, в частности, с влиянием Remix, полноценного веб-фреймворка, разработанного теми же авторами.
В этом уроке мы изучили:
- как настроить и установить React Router
- основы маршрутизации и некоторые необходимые компоненты, такие как
<Routes>
,<Route>
и<Link>
- как создать минимальный роутер для навигации и вложенных маршрутов
- как создавать динамические маршруты с параметрами пути
- как работать с хуками React Router и новым паттерном отрисовки маршрута
Наконец, мы изучили некоторые продвинутые техники маршрутизации, создавая завершающее демо для защищенных маршрутов.
Часто задаваемые вопросы
Как работает маршрутизация в React Router v6?
В этой версии внедрен новый синтаксис маршрутизации с использованием компонентов <Routes>
и <Route>
. Компонент <Routes>
оборачивает отдельные компоненты <Route>
, которые указывают путь и элемент, который должен быть отрисован при совпадении пути с URL.
Как настроить вложенные маршруты?
Вложенные маршруты создаются путем помещения компонентов <Route>
внутрь других компонентов <Route>
в коде JSX. Таким образом, вложенные компоненты <Route>
естественным образом отражают вложенную структуру URL-адресов, которые они представляют.
Как перенаправить пользователей на другую страницу?
Вы можете использовать хук useNavigate
для программного перехода пользователей на другую страницу. Например, const navigate = useNavigate();
, а затем navigate('/path');
для перенаправления на нужный путь.
Как передать свойства компонентам?
В версии 6 вы можете передавать свойства компонентам, включая их в атрибуте элемента компонента <Route>
, например: <Route path="/path" element={<Component prop={value} />} />
.
Как получить доступ к параметрам URL в React Router v6?
Параметры URL могут быть получены с помощью хука useParams
. Например, если маршрут определен как <Route path=":id" element={<Component />} />
, вы можете использовать const { id } = useParams();
для доступа к параметру id внутри <Component />
.
Что нового в React Router v6.4?
Версия 6.4 вводит множество новых функций, вдохновленных Remix, таких как загрузчики данных и createBrowserRouter
, с целью улучшения получения и отправки данных. Полный список новых функций можно найти здесь.
Как перейти от React Router v5 к v6?
Миграция включает обновление конфигурации маршрутов на новый синтаксис компонентов <Routes>
и <Route>
, обновление хуков и других методов API до их аналогов в v6, а также разрешение любых проблем совместимости приложения с новой логикой маршрутизации. Официальное руководство можно найти здесь.
Leave a Reply