
Вольный перевод публикации How React Hooks can replace React Router.
Автор Peter Ekene Eze.
С момента появления React Hooks многое изменилось. Возможности, предоставляемые хуками, позволили пересмотреть наш подход к определенным концепциям в React, в том числе и к маршрутизации.
Этот пост никоим образом не предназначен для того, чтобы списывать со счетов React Router или приуменьшать его важность. Мы изучим другие возможнос��и и посмотрим, как мы можем улучшить работу с маршрутизацией в приложениях React, используя хуки.
С этой целью мы сравним React Router и hooksrouter на наглядных примерах. Для начала давайте поближе посмотрим на React Router.
React Router
React Router — это популярный декларативный способ управления маршрутами в приложениях React. Он уменьшает объем работ, связанных с ручной настройкой маршрутов для всех страниц и экранов в приложении. React Router предоставляет нам три основных компонента, которые помогают нам сделать маршрутизацию — Route, Link и BrowserRouter.
Маршрутизация в React Router
Представим, что мы создаем React приложение, состоящее из трех страниц. Обычно для маршрутизации мы используем React Router и пишем примерно такой код:
import Users from "./components/Users"; import Contact from "./components/Contact"; import About from "./components/About"; function App() { return ( <div> <Router> <div> <Route path="/about" component={About} /> <Route path="/users" component={Users} /> <Route path="/contact" component={Contact} /> </div> </Router> </div> ); }
Компонент <Route />, импортированный из пакета React Router, принимает два параметра: путь, по которому переходит пользователь, и компонент для отображения по указанному пути.
Хуки как альтернативная маршрутизация
Модуль hookrouter экспортирует хук useRoutes(), который проверяет предопределенный объект маршрутов и возвращает результат. В объекте маршрутов вы определяете свои маршруты как ключи, а их значения — как функции, которые будут вызываться при совпадении маршрутов. Пример кода на хуках:
import Users from "./components/Users"; import Contact from "./components/Contact"; import About from "./components/About"; const Routes = { "/": () => , "/about": () => , "/contact": () => }; export default Routes;
Такой вариант написания маршрутов более привлекательный. Почему? Потому что нам не потребовалось выполнять много работы. С React Router нам пришлось отрисовать компонент <Route /> для всех отдельных маршрутов в нашем приложении. Не говоря уже обо всех параметрах, которые мы передали ему. С помощью хуков, мы можем использовать наши маршруты, просто передав их в хук useRoutes():
import {useRoutes} from 'hookrouter'; import Routes from './router' function App() { const routeResult = useRoutes(Routes) return routeResult }
Это дает нам точно такой же результат, который мы получили бы, используя React Router, но с более чистой и легкой реализацией.
React Router навигация
React Router ��ключает в себя компонент <Link/>, который помогает нам настраивать навигацию по приложению и управлять интерактивной маршрутизацией. У нас есть приложение с тремя страницами, давайте отобразим их на экране и перейдем к ним при нажатии:
import { Route, Link, BrowserRouter as Router } from "react-router-dom"; import Users from "./components/Users"; import Contact from "./components/Contact"; import About from "./components/About"; function App() { return ( <div className="App"> <Router> <div> <ul> <li> <Link to="/about">About</Link> </li> <li> <Link to="/users">Users</Link> </li> <li> <Link to="/contact">Contact</Link> </li> </ul> <Route path="/about" component={About} /> <Route path="/users" component={Users} /> <Route path="/contact" component={Contact} /> </div> </Router> </div> ); }
Мы создали навигацию, которая нужна для перехода с одной страницы приложения на другую. Вот наглядный пример того, как это работает.

Используем хуки для навигации
Модуль hookrouter предоставляет обертку <A/> над HTML-тегом <a/>. Он доступен как react компонент и на 100% совместим с нативным тегом <a/>. Единственное отличие состоит в том, что он перемещает навигацию в стек истории вместо фактической загрузки новой страницы.
const routes = { "/user": () => <Users />, "/about": () => <About />, "/contact": () => <Contact /> }; function App() { const routeResult = useRoutes(routes); return ( <div className="App"> <A href="/user">Users Page</A> <A href="/about">About Page</A> <A href="/contact">Contacts Page</A> {routeResult} </div> ); }

Программная навигация
Модуль hookrouter дает нам доступ к хуку navigate(), в который мы можем передать URL, и он будет перенаправлять пользователя по этому URL. Каждый вызов функции navigate() представляет собой навигацию вперед, в результате пользователи могут нажать кнопку «Назад» в браузере, чтобы вернуться к предыдущему URL-адресу. Это происходит по умолчанию.
navigate('/user/');
Однако, если вам нужно другое поведение, вы можете сделать замену в истории браузера. Хук navigate() принимает три параметра — navigate(url, [replace], [queryParams]), второй параметр используется для изменения поведения замены. Он удаляет текущую запись истории и заменяет ее новой. Чтобы достичь этого эффекта, просто установите его аргумент в значение true.
navigate('/user', true);
React Router switch
React Router использует компонент <Switch /> для отображения страницы по умолчанию, когда определенные маршруты не совпадают. Обычно он отображает страницу 404, чтобы сообщить пользователю, что выбранный маршрут не определен в приложении. Для этого мы оборачиваем все маршруты внутри компонента <Switch /> и отрисовываем страницу 404, не определяя для нее путь к файлу:
import { Route, Link, BrowserRouter as Router, Switch } from "react-router-dom"; import Users from "./components/Users"; import Contact from "./components/Contact"; import Home from "./components/About"; import NoPageFound from "./components/NoPageFound.js"; function App() { return ( <div className="App"> <Router> <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/users">Users</Link> </li> <li> <Link to="/contact">Contact</Link> </li> </ul> <Switch> <Route exact path="/" component={Home} /> <Route path="/users" component={Users} /> <Route path="/contact" component={Contact} /> <Route component={NoPageFound} /> </Switch> </div> </Router> </div> ); }
Таким образом, при переходе на неопределенный маршрут React Router отображает компонент NoPageFound. Таким способом мы можем информировать пользователей о том, где они находятся и что происходит во время навигации по приложению.
Альтернатива switch на хуках
Поскольку мы определяем объект маршрутов, который содержит все наши маршруты, и просто передаем этот объект в хук useRoutes(), становится очень просто отображать маршруты по условию. Если мы определим компонент NoPageFound для рендеринга по умолчан��ю, когда выбранный маршрут не определен, нам нужно будет только передать этот файл для рендеринга вместе с нашей функцией результата следующим образом:
import { useRoutes, A } from "hookrouter"; import routes from "./router"; import NoPageFound from "./components/NoPageFound"; function App() { const routeResult = useRoutes(routes); return ( <div className="App"> <A href="/user">Users Page</A> <br /> <A href="/about">About Page</A> <br /> <A href="/contact">Contacts Page</A> <br /> {routeResult || <NoPageFound />} </div> ); }

По сравнению с использованием компонента в React Router для рендеринга страниц по умолчанию это выглядит немного чище и более читабельно.
React Router редиректы
Редирект происходит, когда мы хотим динамически направить пользователя с одного маршрута на другой. Например, во время авторизации, когда пользователь успешно входит в систему, мы бы хотели перенаправить его с маршрута '/ login' на маршрут '/ dashboard'.
С React Router мы можем сделать это несколькими способами — используя объект истории или компонент <Redirect />. Например, если у нас есть форма входа в систему, мы можем использовать объект истории браузера, чтобы перенаправить пользователя к маршруту '/ dashboard' при входе в систему:
import React from 'react' class Login extends React.Component { loginUser = () => { // if (user is logged in successfully) this.props.history.push('/dashboard') } render() { return ( <form> <input type="name" /> <input type="email" /> <button onClick={this.loginUser}>Login</button> </form> ) } } export default Login
Редиректы с помощью хуков
Модуль hookrouter предоставляет нам хук useRedirect(), который может принимать исходный маршрут и целевой маршрут в качестве параметров.
useRedirect('/user', '/dashboard');
Это автоматически перенаправит пользователей на маршрут '/dashboard', когда будет найден путь '/user'. Например, если мы не хотим показывать каких-либо пользователей, а вместо этого автоматически перенаправляем пользователя на его /dashboard, мы напишем это так:
import {useRoutes, useRedirect} from 'hookrouter'; import dashboard from "./components/Dashboard"; const routes = { '/home': () => <Users />, '/dashboard': () => <Dashboard /> }; const Users = () => { useRedirect('/user', '/dashboard'); const routeResult = useRoutes(routes); return routeResult }

Стоит отметить, что хук useRedirect() вызывает замену истории навигации. В результате в истории навигации будет только одна запись. Это означает, что если перенаправление происходит с '/user' на '/dashboard, как было в последнем, рассматриваемом фрагменте кода, маршрут '/user' не будет отображаться в истории просмотра. У нас будет только маршрут '/ dashboard'.
Обработка параметров URL в React Router
Параметры URL помогают нам отображать компоненты на основе их динамических URL. Это работает аналогично с вложенными маршрутами, однако в этом случае маршруты не меняются, а обновляются.
Например, если бы в нашем приложении были разные пользователи, имело бы смысл идентифицировать их отдельно с их индивидуальными маршрутами, такими как 'user/user1/' и 'users/user2/' и т.д. Для этого нам нужно использовать параметры URL. В React Router мы просто передаем параметр (например, id), начинающийся с двоеточия, в свойстве path у компонента <Route />:
<Route path="users/:id" component={Users} />
Используем хуки для обработки параметров URL
Практически нет отличий в обработке параметров URL в hookrouter и React Router. Конструкция та же (т.е. вы можете передать параметры URL-адреса целевым маршрутам, используя двоеточие и имя параметра).
Тем не менее, есть разница в том, как работает hookrouter. Он читает все параметры URL и помещает их в объект. Это делается с помощью ключей, которые вы определили в объекте маршрутов. Затем все названные параметры будут перенаправлены в функцию результата вашего маршрута как объединенный объект.
const routes = { '/user/:id': ({id}) => <User userId={id} /> }
Используя деструктуризацию объекта, мы просто берем свойство id из объекта props и затем применяем его к нашему компоненту. Таким образом, мы получаем точно такой же результат, как и с React Router.
Заключение
Мы рассмотрели альтернативный способ маршрутизации в React приложениях. React Router — отличный инструмент, однако, с появлением хуков в React многое изменилось, в том числе и то, как работает маршрутизация. Этот модуль на основе хуков предлагает более гибкий и чистый способ обработки маршрутов в небольших проектах.
Помимо основного функционала, рассмотренного в посте, hookrouter имеет ряд дополнительных возможностей, таких как:
- Lazy loading компонентов
- Вложенные маршруты
- Передача дополнительных данных в маршрут
- Перехват навигации
- Поддержка серверного рендеринга
У проекта hookrouter отличная документация, в которой очень быстро можно найти интересующий вопрос.
В статье были использованы материалы из документации к hookrouter
и оригинальной статьи How React Hooks can replace React Router.
Git-репозиторий hookrouter
UPD: при оформлении статьи не увидел сразу, что можно выбрать тип публикации «Перевод», прошу прощения у читателей, оформил в шапке ссылку на оригинал и указал автора.
