React Router, версия 6. Часть 1 из 4

20.12.2021

Теги: FrontendJavaScriptReact.jsWeb-разработкаМаршрутизацияМодуль

Совсем недавно разбирался с роутингом в 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-разработка • Маршрутизация • Модуль

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.