React Router, версия 6. Часть 1 из 4
20.12.2021
Теги: Frontend • JavaScript • React.js • Web-разработка • Маршрутизация • Модуль
Совсем недавно разбирался с роутингом в react-приложениях, но вышла 6-ая версия модуля react-router-dom
— и пришлось разбираться заново. В новой версии больше нет Switch
, теперь нужно использовать Routes
, вместо атрибута component
внутри Route
теперь атрибут element
. Атрибуты path
и to
внутри Route
и Link
теперь могут быть относительными, то есть дополнять path
и to
родителя, а вместо useHistory
теперь useNavigate
.
Подготовительный этап
Нам потребуется развернуть react-приложение и установить пакет react-router-dom
:
> npx create-react-app@latest app > cd app > npm install react-router-dom@latest
Первым делом обернем все наше приложение в компонент BrowserRouter
в файле src/index.js
:
import React from 'react' import ReactDOM from 'react-dom' import { BrowserRouter } from 'react-router-dom' import './index.css' import App from './App.js' ReactDOM.render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode>, document.getElementById('root') )
Добавим минимальные стили в src/index.css
, чтобы наше приложение выглядело более-менее прилично:
* {
box-sizing: border-box;
}
:root {
--color-bg: #2C2C32;
--color-active: #FF9999;
}
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
header {
background-color: var(--color-bg);
padding: 2rem 0;
text-align: center;
}
header > a {
color: white;
text-decoration: none;
padding: 0 1rem;
}
header > a:hover {
text-decoration: underline;
}
h1 {
text-align: center;
margin-top: 0;
}
.container {
max-width: 900px;
padding-left: 2rem;
padding-right: 2rem;
margin: 0 auto;
}
main {
min-height: calc(100vh - 85px - 22px);
padding-top: 1.5rem;
padding-bottom: 1.5rem;
}
main a {
text-decoration: none;
color: var(--color-bg);
}
main a:hover {
text-decoration: underline;
}
.active {
color: var(--color-active);
cursor: default;
}
В src/App.js
у нас будут шапка, подвал и основной контент:
function App() { return ( <> <header> <a href="/">Home</a> <a href="/blog">Blog</a> <a href="/about">About</a> </header> <main> <h1>React Router v6</h1> </main> <footer>Copyright 2021</footer> </> ) } export default App
Страницы приложения
Создадим директорию src/pages
и разместим в ней четыре компонента Home.js
, Blog.js
, About.js
и NotFound.js
.
const Home = () => { return <h1>Home page</h1> } export default Home
const Blog = () => { return <h1>Our blog</h1> } export default Blog
const About = () => { return <h1>About page</h1> } export default About
const NotFound = () => { return <h1>Not Found</h1> } export default NotFound
Базовый роутинг
Теперь можно уже создавать базовый роутинг нашего приложения, редактируем src/App.js
:
import { Routes, Route } from 'react-router-dom' import Home from './pages/Home' import Blog from './pages/Blog' import About from './pages/About' import NotFound from './pages/NotFound' function App() { return ( <> <header> <a href="/">Home</a> <a href="/blog">Blog</a> <a href="/about">About</a> </header> <main> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> <Route path="/about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Routes> </main> <footer>Copyright 2021</footer> </> ) } export default App
Link и NavLink
Ссылки в <header>
теперь работают, но страницы целиком загружаются с сервера. Вместо обычных ссылок мы должны использовать компоненты Link
и NavLink
— давайте сделаем это.
import { Routes, Route, Link } from 'react-router-dom' import Home from './pages/Home' import Blog from './pages/Blog' import About from './pages/About' import NotFound from './pages/NotFound' function App() { return ( <> <header> <Link to="/">Home</Link> <Link to="/blog">Blog</Link> <Link to="/about">About</Link> </header> <main> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> <Route path="/about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Routes> </main> <footer>Copyright 2021</footer> </> ) } export default App
Вот теперь у нас SPA, переход по ссылкам не вызывает загрузку каждой страницы с сервера. Если вместо Link
использовать NavLink
— мы можем выделить текущую страницу, причем нам даже ничего не надо делать. NavLink
для ссылки на текущую страницу добавляет css-класс active
— а поскольку такой класс в index.css
есть, то ссылка будет выделена цветом.
Если css-класс не active
, а current
— нужно передать атрибуту className
функцию, которая вернет имя css-класса:
import { Routes, Route, NavLink } from 'react-router-dom' import Home from './pages/Home' import Blog from './pages/Blog' import About from './pages/About' import NotFound from './pages/NotFound' const activeClass = ({isActive}) => isActive ? 'current' : '' function App() { return ( <> <header> <NavLink to="/" className={activeClass}>Home</NavLink> <NavLink to="/blog" className={activeClass}>Blog</NavLink> <NavLink to="/about" className={activeClass}>About</NavLink> </header> <main> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> <Route path="/about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Routes> </main> <footer>Copyright 2021</footer> </> ) } export default App
Кроме того, можно передать аналогичную функцию атрибуту style
— но нужно запретить добавление по умолчанию css-класса active
, чтобы выделение ссылки на текущую страницу было только с помощью inline-стилей, без использования css-класса.
import { Routes, Route, NavLink } from 'react-router-dom' import Home from './pages/Home' import Blog from './pages/Blog' import About from './pages/About' import NotFound from './pages/NotFound' const activeStyle = ({isActive}) => ({color: isActive ? 'var(--color-active)' : 'white'}) function App() { return ( <> <header> <NavLink to="/" className={() => ''} style={activeStyle}>Home</NavLink> <NavLink to="/blog" className={() => ''} style={activeStyle}>Blog</NavLink> <NavLink to="/about" className={() => ''} style={activeStyle}>About</NavLink> </header> <main> <Routes> <Route path="/" element={<Home />} /> <Route path="/blog" element={<Blog />} /> <Route path="/about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Routes> </main> <footer>Copyright 2021</footer> </> ) } export default App
Исходные коды приложения здесь, директория 1.
Outlet
Как правило, любой сайт имеет шапку, подвал и основной контент. Шабка и подвал не изменяются от страницы к странице, а вот контент у каждой страницы свой. Разработчики React Router учли это обстоятельство и предлагают использовать компонент Outlet
. Общая идея такова — есть некий layout-компонент и в нем есть заглушка, на место которой вставляется вложенный компонент.
Создадим в директории src/pages
компонент AppLayout.js
:
import { NavLink, Outlet } from 'react-router-dom' const AppLayout = () => { return ( <> <header> <NavLink to="/">Home</NavLink> <NavLink to="/blog">Blog</NavLink> <NavLink to="/about">About</NavLink> </header> <main> <Outlet /> </main> <footer>Copyright 2021</footer> </> ) } export default AppLayout
Теперь компонент App.js
будет намного проще. При этом верхний Route
содержит все остальные Route
.
import { Routes, Route } from 'react-router-dom' import AppLayout from './pages/AppLayout.js' import Home from './pages/Home.js' import Blog from './pages/Blog.js' import About from './pages/About.js' import NotFound from './pages/NotFound.js' function App() { return ( <Routes> <Route path="/" element={<AppLayout />}> <Route index element={<Home />} /> <Route path="blog" element={<Blog />} /> <Route path="about" element={<About />} /> <Route path="*" element={<NotFound />} /> </Route> </Routes> ) } export default App
Пути blog
и about
теперь без слэша в начале — это относительные пути по отношению к вышестоящему Route
.
Поиск: Frontend • JavaScript • React.js • Web-разработка • Маршрутизация • Модуль