Реакт Роутер v6 Руководство для начинающих – CodesCode

Изучите, как навигироваться по приложению React c несколькими представлениями с помощью React Router - стандартной библиотеки маршрутизации для React.

React Router является стандартной библиотекой маршрутизации для React. Когда вам нужно навигировать по приложению React с несколькими видами, вам понадобится маршрутизатор для управления URL-адресами. React Router заботится об этом, поддерживая синхронизацию пользовательского интерфейса приложения и URL-адреса.

В этом руководстве вы познакомитесь с React Router v6 и узнаете о множестве вещей, которые вы можете сделать с его помощью.

Введение

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, о которых нам нужно сначала рассказать.

В версии 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

Your email address will not be published. Required fields are marked *