Что такое SPA?
SPA (single page application) переводится как “одностраничное приложение”, очень распространенный способ программирования веб-сайтов в наши дни: идея в том, что сайт загружает весь нужный для пользовательского опыта HTML/JS сразу же при первом посещении главной страницы, а при последующих переходах по страницам браузер лишь просматривает содержимое заново, не обновляя сайт.
Одностраничные приложения на JavaScript-фреймворках улучшают удобство веб-сайта для посетителя благодаря непрерывности пользовательского опыта; перейдя на страницу веб-сайта вы сразу сможете определить — это SPA или многостраничное приложение: всего лишь быстро нажмите несколько ссылок в навигации. Многостраничное приложение будет перезагружаться, заставляя весь пользовательский интерфейс быстро мигать в зависимости от содержимого, происходит подобное из-за обновления сайта. Напротив, SPA плавно отображается всегда, в любой момент, поскольку такое приложение просто показывает пользователю другой контент без обновления страницы веб-сайта в его браузере.
В руководстве рассмотрим, как написать single page application с помощью React Router.
Оглавление:
- Что такое SPA?
- Приложение на React с нуля.
- React Router: маршрутизация в React.
- UseParams: передача данных в ссылку.
- UseLocation: передача данных через React Router.
- UseHistory: программная маршрутизация.
- Выводы.
Приложение на React с нуля
Сначала создайте новый файл-шаблон приложения, выполните команду:
create-react-app app
Войдите в только что созданную папку app
и установите React-Router, необходимый для написания SPA.
npm i react-router-dom --save
Для начала измените только что созданное приложения: откройте файл src/App.js
и замените весь код из него на следующий:
import './App.css';
import Main from './layouts/main';
function App() {
return (
<Main></Main>
);
}
export default App;
Уже заметили, что функция App()
возвращает компонент <Main></Main>
? Соответственно, следующий шаг в разработке SPA — написать компонент <Main>
, чтобы приложение не упало. Создайте новую папку layouts
, а в ней — файл layouts/main.js
: в данном файле будем хранить базовый макет приложения.
mkdir layouts
touch layouts/main.js
Для начала, мы создадим в файле main.js
навигационную панель; она будет выглядеть недостаточно хорошо с точки зрения дизайна, но ведь вы читаете руководство ради изучения React Router, а не ради готовой CSS-вёрстки. Навигация содержит ссылки на страницу с информацией о сайте и страницу с контактами.
Сразу под навигацией создадим блок <div></div>
с контентом для отображения расширенной информации при пользовательском нажатии на выбранную ссылку; такие контент-блоки — это крайне распространенная настройка для одностраничного приложения на React: создаётся статичный макет сайта с навигацией и карточка, динамически меняющая свое содержимое. Рассмотрите следующий пример повнимательнее:
import Navbar from '../components/navbar/navbar'
function Main() {
return (
<div>
<Navbar></Navbar>
<div className="content">
</div>
</div>
)
}
export default Main;
Main.js — это макет приложения по умолчанию
Теперь давайте расширим ссылками элемент навигационной панели, а затем посетим только что созданную страницу: создайте в существующей директории src
новую директорию с названием components
, в ней мы сохраним все компоненты React-приложения; затем создайте файл components/navbar/navbar.js
.
mkdir -p components/navbar/
touch components/navbar/navbar.js
function Navbar() {
return (
<div className="navbar">
<h1>Navbar</h1>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">about</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</div>
)
}
export default Navbar;
Navbar.js — простой список ссылок для навигации
Наконец, откройте файл src/index.css
и вставьте следующий фрагмент CSS, ведь он поможет красиво отобразить навигационную панель в виде горизонтального списка:
.navbar ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
}
.navbar li {
float: left;
}
.navbar li a {
display: block;
padding: 8px;
background-color: #dddddd;
}
index.css — добавление горизонтальной навигационной панели
Запустите приложение и откройте в браузере ссылку localhost:3000
, чтобы полюбоваться на свои труды — ваше первое React single page application:
npm start
Прежде чем приступить к реализации React Router, нам нужно создать три очень простых компонента: они будут только лишь выводить на экран свое название.
mkdir components/about
mkdir components/contact
mkdir components/home
function About() {
return (
<h1>About</h1>
)
}
export default About;
Скопируйте и вставьте приведенный выше код в три созданных ранее файла компонентов, лишь измените в каждом из них название на соответствующее конкретной странице сайта. Таким образом, у вас должны получиться следующие компоненты React-приложения: components/about/About.js
, components/contact/Contact.js
и components/home/Home.js
, причём код во всех этих файлах должен быть одинаково похож на пример выше, лишь с измененными названиями внутри тегов <h1></h1>
.
React Router
Маршрутизация в React
Прежде чем продолжать разработку, убедитесь, что вы верно написали все три компонента-файла, ведь наконец-то пришло время узнать, как работает React Router: давайте сделаем так, чтобы при нажатии на навигационные ссылки отображаемое содержимое веб-сайта менялось на контент из соответствующего компонента, создавая для пользователя видимость перехода на новую страницу.
Для этого потребуется изменить два файла: в файле layouts/main.jsh
необходимо подключить собственно Router
, а в файле components/navbar/navbar.js
придётся заменить все теги <a>
на компонент Link
из React Router
. Не беспокойтесь, в руководстве детально рассматривается каждый шаг разработки, так что вы сможете досконально изучить, что делает каждая строчка кода и что представляет собою каждый из компонентов приложения на React.
Начнем с добавления в main.js
следующего импорта:
import { HashRouter, Switch, Route } from "react-router-dom";
Импорт необходимых компонентов из react-router-dom
Прежде чем двигаться дальше, рассмотрим, что представляет собою каждый из компонентов: это очень важно, так как понимание действий каждого из компонентов React-приложения не только облегчает их использование, но и позволяет добиться более высокого качества кода, чем общий анализ шаблона.
- Маршрутизатор (Router) — это общий низкоуровневый интерфейс для всех компонентов маршрутизации в приложении; по умолчанию React предоставляет несколько различных маршрутизаторов: мы применим на практике
HashRouter
, но есть и обычныйBrowserRouter
. - HashRouter — это подвид маршрутизатора с использованием символа
#
в URL для синхронизации пользовательского интерфейса: как пример — ссылка видаhttp://localhost:3000/#/about
. Для начала подключим к приложению данный маршрутизатор, а затем вы научитесь его отключать; применяется Hash-маршрутизатор на статических веб-сайтах. - BrowserRouter — это маршрутизатор
<Router>
с применением HTML5 history API (pushState
,replaceState
и событиеpopstate
) для синхронизации пользовательского интерфейса с URL. Данный маршрутизатор также подключим к приложению; его следует применять тогда, когда серверу предстоит обрабатывать динамические запросы. - Маршрут (Route) — это компонент, отображающийся всякий раз при соответствии URL-адреса заданному шаблону. Следовательно, маршрутов одновременно может быть много: вот почему так важен переключатель.
- Переключатель (Switch) — это компонент, с помощью которого контролируется отображение различных маршрутов (Routes); он отображает только первый подходящий дочерний маршрут. Отлично подходит, чтобы избежать возникновения ошибки одновременного отображения нескольких маршрутов.
- Ссылка (Link) — это компонент, позволяющий маршрутизатору изменять навигацию в приложении.
- Навигационная ссылка (NavLink) — это просто расширение компонента ссылки, применяющееся в создании стилей: оно добавляет класс
.activate
к текущей выбранной ссылке в навигационной панели — таким образом пользователь поймёт, какая именно ссылка выбрана в данный конкретный момент.
Теперь создадим HashRouter
, внутрь которого переместим всё содержимое файла main.js
: это необходимо, чтобы убедиться в правильном использовании компонентов, например, если компонент Route
определён вне HashRouter
, то он не сработает. Поэтому HashRouter
необходимо сделать родителем, поместив внутрь него весь остальной код; именно по этой причине маршрутизаторы часто встречаются в App.js
.
Подсказка: позже мы заменим HashRouter
на BrowserRouter
.
Переключатель Switch
размещается внутри компонента div
с контентом, поскольку именно там отображается содержимое (контент) каждой новой выбранной страницы.
Помните: компонент div
с контентом отображается при каждом совпадении маршрута Route
с конкретным URL, а переключатель Switch
следит, чтобы отобразилось только первое совпадение.
Все маршруты Route
определены внутри переключателя Switch
, поскольку одновременно на странице отображается только один компонент; однако нередко встречается вариация с определением нескольких маршрутов вне переключателя. Вот код:
import Navbar from '../components/navbar/navbar'
import { BrowserRouter, HashRouter, Switch, Route } from "react-router-dom";
import Home from '../components/home/home'
import Contact from '../components/contact/contact'
import About from '../components/about/about'
function Main() {
return (
<div>
<HashRouter>
<Navbar></Navbar>
<div className="content">
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/contact" component={Contact}/>
<Route path="/about" component={About}/>
</Switch>
</div>
</HashRouter>
</div>
)
}
export default Main;
Main.js
— полный пример программы с перенаправлением на нужный компонент в зависимости от URL-адреса
Далее необходимо изменить Navbar.js
так, чтобы он содержал специальные компоненты NavLink
вместо тегов <a>
, ведь маршрутизатор будет работать только с компонентами Link
.
Вы заметили, что путь /
содержит ключевое слово? Оно необходимо из-за того, что такой путь по умолчанию соответствует ВСЕМ ВОЗМОЖНЫМ путям, ведь все они содержат данный символ. Помните, какую ссылку выберет переключатель Switch
? Именно первое совпадение, поэтому необходимо явно указать, что выбирать путь /
нужно только в случае полного, абсолютного совпадения. Пример программы:
import { NavLink } from "react-router-dom";
function Navbar() {
return (
<div className="navbar">
<h1>Navbar</h1>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/contact">Contact</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
</ul>
</div>
)
}
export default Navbar;
Navbar.js
— полный код навигационной панели с NavLinks
вместо тегов <a></a>
По желанию измените свойство CSS .active
в файле index.css
, визуально выделив активную навигационную ссылку.
.active {
color: #ffffff;
}
index.css
— визуальное выделение активной навигационной ссылки
Откройте страницу по адресу localhost:3000
, чтобы оценить результат и протестировать навигацию по сайту. Если все этапы разработки выполнены правильно, то вы должны увидеть одну особенность поведения веб-страницы: содержимое переключается, в то время как верхняя панель навигации остается неизменной.
UseParams
Передача данных в ссылку
Одна из ключевых особенностей ссылок заключается в возможности передачи разных данных в компоненты, что довольно важно, например, при настройке профиля пользователя.
Рассмотрим два способа передачи данных в маршрутизаторе.
Первый способ — это хук useParams
из react-router
, позволяющий передавать пары ключ-значение в URL.
Теперь напишем профиль пользователя и выведем его имя на витрину.
Сначала создайте новую папку user
, далее — добавьте в неё файл components/user/profile.js
.
mkdir components/user
touch components/user/profile.js
В файле profile.js
импортируем useParams
, затем присваиваем значение имени пользователя с помощью данного хука. Вот код:
import { useParams } from "react-router";
function Profile() {
// Для получения имени пользователя из URL примените хук useParams.
// Имя пользователя должно указывается в качестве именованного параметра маршрута.
const { username } = useParams();
return (
<h1>{username} Profile</h1>
)
}
export default Profile;
profile.js
— useParams
позволяет извлечь имя пользователя из URL-адреса
Как useParams
узнаёт, какое именно значение извлекать? При помощи именованных параметров из объявления маршрута, конечно же; параметры именуются путем добавления :parameter
к Route
. Поэтому теперь измените маршрут в main.js
, добавив к нему параметр :username
следующим образом:
import { useParams } from "react-router";
function Profile() {
// Для получения имени пользователя из URL примените хук useParams.
// Имя пользователя должно указывается в качестве именованного параметра маршрута.
const { username } = useParams();
return (
<h1>{username} Profile</h1>
)
}
export default Profile;
main.js — добавление маршрута профиля с параметром :username
Последнее, что нужно изменить — навигационная панель должна показывать страницу профиля и вставлять значение имени пользователя в ссылку. Для примера всё жестко закодировано, но в реальном сценарии ваша панель навигации почти всегда получает имя пользователя откуда-то ещё.
import { NavLink } from "react-router-dom";
function Navbar() {
return (
<div className="navbar">
<h1>Navbar</h1>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/contact">Contact</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to="/profile/percybolmer">Profile</NavLink></li>
</ul>
</div>
)
}
export default Navbar;
Navbar.js
— добавление ссылки на profile/percybolmer
, где percybolmer
— значение имени пользователя, жестко закодированное для примера
Теперь настало время снова посетить ваш веб-сайт, посмотреть на результаты ваших трудов. Обратите внимание, что URL-адрес явно показывает значение имени пользователя. Попробуйте изменить URL-адрес в браузере, чтобы увидеть и проанализировать изменения.
useLocation
Передача данных через React Router
Как вы уже догадались, не стоит добавлять слишком много URL-параметров, более того, с помощью параметра передаются только строки, а вам, скорее всего, понадобится передавать куда более разнообразные данные.
К счастью, всё возможно благодаря state
, переменной состояния в ссылках: каждая ссылка может иметь свой параметр to
для получения данных, опосля передающихся через переменную состояния с помощью хука useLocation
.
В объекте to
обязательно должны устанавливаться переменная состояния state
и переменная pathname
— это конечная точка URL.
{
"pathname": "url",
"state": {
"yourstatevar": "value"
}
}
И вот теперь вы думаете: “Отлично, пора запускать!”; однако на всех форумах вроде Stack Overflow регулярно задаются вопросы, которые мы сразу же и рассмотрим в данном руководстве.
“Да, я специально допустил ошибки, чтобы убедиться, что мы их устраним.” — Злой Учитель Программирования.
Две самые распространенные среди новичков проблемы:
HashRouter
не поддерживает хукuseLocation
- Нельзя размещать маршрутизатор
Router
внутри компонента, который размещает маршрутизатор в дереве.
Для начала разберёмся с первой проблемой — несовместимостью HashRouter
и хука useLocation
. Важно это знать, а также очень важно подчеркнуть это в документации. Именно из-за данной проблемы мы заменим HashRouter
на BrowserRouter
. Единственное изменение, которое нужно сделать — изменить идентификатор и убедиться, что BrowserRouter
корректно импортирован.
А пока мы меняем маршрутизатор, то нужно решить проблему номер два: просто перенесите Router
в файл index.js
, что заставит компонент Router
обернуть всё приложение и, таким образом, он перестанет быть частью компонента, добавляющего сам Router
в дерево. Вот код:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root')
);
// Если вы хотите измерить производительность приложения, то передайте функцию
// для регистрации результатов (например: reportWebVitals(console.log))
// или отправьте её в конечную точку аналитики.
reportWebVitals();
Index.js
— маршрутизатор браузера оборачивает приложение
Далее удалим маршрутизатор из файла main.js
:
import Navbar from '../components/navbar/navbar'
import { Switch, Route } from "react-router-dom";
import Home from '../components/home/home'
import Contact from '../components/contact/contact'
import About from '../components/about/about'
import Profile from '../components/user/profile'
function Main() {
return (
<div>
<Navbar></Navbar>
<div className="content">
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/contact" component={Contact}/>
<Route path="/about" component={About}/>
<Route path="/profile/:username" component={Profile}/>
</Switch>
</div>
</div>
)
}
export default Main;
Main.js — в файле больше нет маршрутизатора
Затем слегка перепишем код в файле navbar.js
, ведь необходимо убедиться, что объект to
передает больше данных, чем раньше. Код приведен ниже:
import { NavLink } from "react-router-dom";
function Navbar() {
return (
<div className="navbar">
<h1>Navbar</h1>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/contact">Contact</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to={{
pathname: "/profile/percybolmer",
state: { registrationdate: "2021-07-07" },
}}>Profile</NavLink></li>
</ul>
</div>
)
}
export default Navbar;
Функция Navbar с объектом to в ссылке
И напоследок пропишем хук useLocation
; из примера вы узнаете, как присвоить и переменную State
, и переменную pathname
. Данный сниппет почти ничем не отличается от применения хука useParams
:
import { useParams, useLocation } from "react-router-dom";
import React from 'react';
const Profile = () => {
// Используйте хук useParams для получения имени пользователя из URL.
// Имя пользователя должно быть применено в качестве именованного параметра в маршруте.
let { username } = useParams();
// useLocation применяется для захвата состояния из входных данных в объект.
// Так можно захватить каждое поле в объекте, используя то же имя, что и имя переменной.
let { pathname } = useLocation();
let { state } = useLocation();
return (
<div>
<h1>{username} Profile</h1>
<p> Registered on:{state.registrationdate} </p>
<p> Visiting: {pathname}</p>
</div>
)
}
export default Profile;
Profile.js — хук useLocation применяется дважды
Снова перейдите по ссылке localhost:3000
, проанализируйте поведение и внешний вид веб-сайта. Заметьте, как изменились URL после удаления HashRouter
:
useHistory
Программная маршрутизация
Иногда требуется программно перенаправлять пользователя по ссылке на основе какого-либо события: вам пригодится хук useHistory
, возвращающий объект истории для перенаправления браузера по нужному пути. Данный хук крайне прост в применении: при одновременном использовании объекта history
и push(route)
произойдёт перенаправление браузера.
Теперь давайте сделаем тег <h1></h1>
в навигационной панели кнопкой, перенаправляющей пользователя на домашнюю страницу при нажатии на неё.
Далее мы применим тот же самый подход, что и раньше: присвоим переменную с помощью хука, на этот раз — useHistory
.
import { NavLink , useHistory} from "react-router-dom";
function Navbar() {
let history = useHistory();
function goHome(path) {
history.push("/");
}
return (
<div className="navbar">
<button type="button" onClick={goHome}><h1>Navbar</h1></button>
<ul>
<li><NavLink exact to="/">Home</NavLink></li>
<li><NavLink to="/contact">Contact</NavLink></li>
<li><NavLink to="/about">About</NavLink></li>
<li><NavLink to={{
pathname: "/profile/percybolmer",
state: { registrationdate: "2021-07-07" },
}}>Profile</NavLink></li>
</ul>
</div>
)
}
export default Navbar;
navbar.js — пример useHistory для перенаправления браузера
Наконец-то в последний раз перейдите по ссылке localhost:3000
и посмотрите на финальный результат своих трудов. Теперь перейдите на любую страницу, кроме стартовой по адресу /
, а затем нажмите на кнопку: вуаля, вас перенаправило обратно на главную!
Читайте также:
- 1 Кодовая база - 3 платформы
- Создаем темный режим, используя React и Styled Components
- React SPA SEO с Prerender.io
Читайте нас в Telegram, VK и Дзен
Перевод статьи Percy Bolmér: How To Implement a Single Page Application Using React-Router