Перейти к основному содержимому
Блокчейн47ГЛАВА 47Навигация: несколько экранов в одном приложении/ops/0xabc//ops*OpCardбез перезагрузки

Навигация: несколько экранов в одном приложении

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Интерфейс к контракту — это несколько экранов: баланс, форма вклада, список операций, карточка одной операции, страница администратора. Складывать их в один экран нельзя, а перезагружать страницу на каждый переход — значит терять подключение к кошельку и всё состояние.

Выход известен: адрес меняется, страница остаётся. Браузер это умеет, и на этом умении построены все библиотеки навигации.

Эта глава — про то, как устроен механизм под ними, как выглядит работа с маршрутами и про одну ловушку, которую находят в последний час: переходы работают, а обновление страницы даёт ошибку.

Адрес меняется, документ остаётся​

Прогон меняет адрес из кода и смотрит, что при этом происходит с документом:

── смена адреса из кода ──
было : /docs/blockchain/web-html/
после pushState : /ops?kind=deposit
метка на месте : да — документ тот же, перезагрузки не было
заголовок страницы не изменился: "HTML: из чего состоит страница…"
ещё один шаг : /ops/0xabc
после «назад» : /ops?kind=deposit
поймано popstate: ["/ops?kind=deposit"]
состояние шага : {"screen":"ops"}
АДРЕС МЕНЯЕТСЯ, ДОКУМЕНТ ОСТАЁТСЯ ТОТ ЖЕбыло/docs/blockchain/web-html/pushState/ops?kind=depositpushState/ops/0xabcкнопка «назад»/ops?kind=depositчто при этом не менялосьобъект, созданный до сменызаголовок страницыни одной загрузки с сервераpopstate: /ops?kind=depositбраузер умеет менять адрес и историю, не перезагружая страницу — на этом стоит вся навигациявместе с адресом можно сохранить состояние шага: при возврате оно приходит обратно
Три смены адреса и возврат назад — без единой загрузки с сервера
Нажми на часть выражения

Метка на месте, заголовок прежний, объекты живы — документ тот же самый. Именно поэтому подключение к кошельку и загруженные данные переживают переход.

Маршрутизатор: адрес решает, что показывать​

Писать это руками никто не станет: библиотека делает то же самое и добавляет разбор адреса. Сегодня она называется react-router, текущая версия — восьмая; старое имя с окончанием -dom осталось для совместимости.

import { BrowserRouter, Routes, Route, Link, NavLink } from "react-router";

<BrowserRouter>
<nav>
<NavLink to="/">Баланс</NavLink>
<NavLink to="/ops">Операции</NavLink>
</nav>
<Routes>
<Route path="/" element={<Balance />} />
<Route path="/ops" element={<OpsList />} />
<Route path="/ops/:hash" element={<OpCard />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
АДРЕС РЕШАЕТ, КАКОЙ КОМПОНЕНТ ПОКАЗАТЬ/Balanceточное совпадение/opsOpsListсписок; фильтр — в строке запроса/ops/:hashOpCardдвоеточие — переменная часть адреса*NotFoundвсё остальное; без него экран пустойадрес становится частью состояния: его можно послать другому человеку, и он увидит то же самоеобычная ссылка перезагрузила бы страницу — для переходов внутри приложения берут особую
Каждому адресу — свой компонент; звёздочка ловит всё, для чего маршрута нет
Нажми на часть выражения
Важно

Обычная ссылка внутри приложения — самая частая ошибка на первом маршрутизаторе. Внешне всё работает: переход происходит. Но страница перезагружается целиком, подключение к кошельку теряется, и пользователь заново нажимает «подключить».

Что достаётся из адреса​

function OpCard() {
const { hash } = useParams();
return <p>операция {hash}</p>;
}

function OpsList() {
const [params, setParams] = useSearchParams();
const kind = params.get("kind") ?? "все";
return (
<>
<p>фильтр: {kind}</p>
<button onClick={() => setParams({ kind: "deposit" })}>Только вклады</button>
</>
);
}

Переменная часть адреса — то, что стоит после двоеточия в маршруте. Для карточки операции это хеш транзакции.

Строка запроса — то, что после вопросительного знака. Годится для фильтров, сортировки, открытой вкладки: всего, что уточняет экран, но не меняет его.

Отсюда главный приём главы: адрес — это часть состояния. Фильтр, лежащий в адресе, переживает обновление страницы, попадает в закладки и работает при пересылке ссылки. Тот же фильтр, лежащий только в состоянии компонента, не переживает ничего.

Отвечено вопросов: 0 из 2
Что именно делает pushState?
Почему внутри приложения нельзя ставить обычную ссылку?
Отвечено верно: 0 из 2

Ловушка: обновление страницы​

Переходы работают. Пользователь копирует адрес, присылает товарищу — и тот видит ошибку. Или сам нажимает «обновить» — и снова ошибка.

── пользователь нажал «обновить» на адресе приложения ──
/docs/blockchain/web-html/ сервер ответил 200
/ops/0xabc сервер ответил 404
/ops сервер ответил 404
ПОЛЬЗОВАТЕЛЬ НАЖАЛ «ОБНОВИТЬ»/docs/blockchain/web-html/200такой файл на сервере есть/ops404адрес существует только внутри приложения/ops/0xabc404сервер о нём никогда не слышаллечение: сервер на любой неизвестный адрес отдаёт ту же главную страницу, дальше разбирается приложениепереходы внутри приложения работают, а обновление и ссылка из чата — нет: это ловят в последний момент
Адрес существует внутри приложения, но не на сервере: обновление даёт ошибку

Причина простая. Адрес /ops/0xabc придумало приложение, уже загруженное в браузер. На сервере такого файла нет и никогда не было. Пока переход делает приложение, всё хорошо; как только адрес приходит на сервер напрямую — ошибка.

Лечение одинаково у всех: сервер на любой неизвестный адрес отдаёт ту же главную страницу. Приложение загружается, видит адрес и показывает нужный экран.

# при раздаче статики
try_files $uri $uri/ /index.html;

В режиме разработки это уже сделано за вас — поэтому ошибку и находят только после первой настоящей выкладки.

Совет

Проверяется за десять секунд: соберите проект, откройте внутренний адрес и нажмите «обновить». Если вместо экрана ошибка — настройте раздачу. Делать это надо не в день сдачи.

Сколько это стоит​

без маршрутизатора: 222.53 кБ │ gzip: 69.28 кБ
с маршрутизатором : 258.53 кБ │ gzip: 82.30 кБ

Тридцать шесть килобайт, тринадцать при передаче. За это вы получаете разбор адреса, историю, ссылки, вложенные экраны и переменные части пути.

Цифра приведена не для того, чтобы отговаривать, а чтобы показать порядок: библиотеки во фронтенде стоят измеримо, и отчёт сборки эту стоимость показывает. Если один пакет добавил двести килобайт — это повод посмотреть, что именно он делает.

Отвечено вопросов: 0 из 2
Переходы работают, а обновление страницы даёт 404. Почему?
Где должен жить фильтр списка?
Отвечено верно: 0 из 2

Типичные ошибки​

Обычная ссылка вместо ссылки маршрутизатора. Перезагрузка и потерянное подключение.

Нет маршрута «всё остальное». Неизвестный адрес даёт пустой экран без объяснений.

Сервер не настроен на отдачу главной страницы. Переходы работают, обновление — нет.

Фильтр в состоянии вместо адреса. Ссылку на отфильтрованный список нельзя переслать.

Новый шаг истории на каждое нажатие фильтра. Чтобы вернуться, приходится нажать «назад» десять раз. Для таких случаев есть замена текущего шага.

Маршрут с переменной частью выше точного. Порядок в списке маршрутов важен: более общий перехватит то, что предназначалось точному.

Забыли обёртку. Ссылки и маршруты падают с сообщением о том, что они вне маршрутизатора.

Отвечено вопросов: 0 из 2
Фильтр меняется часто, и «назад» приходится нажимать десять раз. Что сделать?
Зачем нужен маршрут со звёздочкой?
Отвечено верно: 0 из 2

Слова главы​

  • Смена адреса из кода — адрес и история меняются, документ остаётся тот же.
  • Состояние шага — объект, сохранённый вместе с адресом; возвращается при переходе назад.
  • Событие «назад/вперёд» — единственный способ узнать о нажатии кнопок браузера.
  • Маршрут — соответствие «адрес → компонент»; со звёздочкой ловит остальное.
  • Переменная часть пути — после двоеточия; достаётся внутри компонента.
  • Строка запроса — уточнение экрана: фильтр, сортировка, вкладка.
  • Ссылка маршрутизатора — перехватывает нажатие вместо перезагрузки.
  • Отдача главной страницы на любой адрес — настройка сервера, без которой обновление даёт ошибку.

Тренажёр: набрать вслепую​

Цель: скорость ≥ 95 зн/мин, точность ≥ 88%

<Route path="/ops/:hash" element={<OpCard />} />

На чемпионате​

Проверьте обновление страницы на внутреннем адресе. Десять секунд сейчас против получаса в день сдачи.

Фильтры и выбранную операцию — в адрес. На защите это выглядит убедительно: вы открываете ссылку, и экран тот же.

Маршрут «не найдено» — обязательно. Пустой экран читается как поломка, даже если всё работает.

Адрес контракта и сеть — тоже кандидаты в адрес страницы. Тогда одну и ту же сборку можно показать на двух сетях, просто открыв разные ссылки.

И про объём: маршрутизатор — первая библиотека, которую вы добавляете сверх самого необходимого. Посмотрите на отчёт сборки до и после. Привычка смотреть на эти числа окупается к третьей библиотеке.

Куда углубиться​

  • Вложенные маршруты — общий каркас и сменная середина.
  • Загрузка данных на уровне маршрута — данные готовы до показа экрана.
  • Защищённые экраны — не пускать, пока кошелёк не подключён.
  • Подгрузка экрана по требованию — не тащить весь код сразу.
  • Навигация на якорях — вариант, который работает без настройки сервера, и чем он плох.

Сводная проверка по всей главе: 8 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоУчимся работать с React Router DOM v7 - делаем роутинг страниц, изучаем хуки, работаем с менюСалимов Руслан IT
ВидеоJavaScript в браузере: Location и History API, URL страницы и история браузераАлександр Ламков — Friendly Frontend
ВидеоМаршрутизация в React без библиотек. Как реализовать переходы между страницамиАлександр Ламков — Friendly Frontend
ВидеоReact с Нуля 12: Руководство React Router. Тот еще МАРШРУТИЗАТОРВсё просто в IT
ВидеоReact Router V7 Tutorial - Routing, Nested Routes, Data Loading, Layouts...PedroTech

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.