image

Вольный перевод публикации 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: при оформлении статьи не увидел сразу, что можно выбрать тип публикации «Перевод», прошу прощения у читателей, оформил в шапке ссылку на оригинал и указал автора.