Подписывайтесь на мой твиттер, там всегда что-нибудь интересное!

Подробно о React Router. Часть 3— редиректы, передача props компоненту роута и вложенные роуты +бонус

В этой серии статей вы подробно и доходчиво узнаете о том, как работает Router в React, как его можно использовать и другие интересные фишки этого функционала. Это завершающая часть, в ней будет рассказано о передаче пропсов компоненту роута, редиректах и вложенных роутах. + бонус, стилизация активных элементов навигации.

Подробно о React Router. Часть 1 — основы роутинга, типы и динамические страницы

Подробно о React Router. Часть 2 — параметры запроса, 404 страница и передача пропсов компоненту роута

p.s статья описывает самый распространенную версию роутера на данный момент. 6я версия сейчас находится в альфе и имеет кардинально другой синтаксис.

Перевод серии статей Deep dive into React Router

Передаём пропсы компоненту роута

Мы видели несколько примеров и рабочих кейсов в React роутере. Один из них это передача пропсов напрямую компоненту самого роута.

В React-router это делается очень легко. Давайте создадим новый роут для передачи props его компоненту.

// App.js
...

const PropsPage = () => {  return ( <h3>Props Page</h3>  );};
const App = () => {
  return (
    <section className="App">
      <Router>
        ...
        <Link to="/404-not-found">404</Link>
        <Link to="/props">Passing Props</Link>        
        <Switch>
          ...
          <Route exact path="/props" component={PropsPage} />              
          <Route component={NoMatchPage} />
        </Switch>
      </Router>
      <a href="/about">about with browser reload</a>
    </section>
  );
};

export default App;

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

Есть два способа это сделать. Первый очень простой.

Передаём функцию как пропс компонента в компоненте роута

...

const PropsPage = ({ title }) => {
  return (
    <h3>{title}</h3>
  );
};

...

<Route exact path="/props-through-component" component={() => <PropsPage title={`Props through component`} />} />

Эта штука сработает, но такой подход не рекомендуется применять с роутером. Почему? Сейчас объясню.

Под капотом React использует React.createElement для рендеринга компонента, переданного пропсу компонента. Если мы передадим ему функцию, то он создаст новый компонент при каждом рендере, вместо того, чтобы просто обновить уже существующий компонент.

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

Роутер даёт нам довольно простое решение в этой ситуации. Вместо простой передачи функции через пропсы component, мы можем передать их через пропсы render. Передавая наши пропсы, нам также надо передать дефолтные пропсы к пропсам рендеринга. Давайте посмотрим пример:

...

<Link to="/props-through-render">Props through render</Link>
...

<Route exact path="/props-through-render" render={(props) => <PropsPage {...props} title={`Props through render`} />} />

Вот это уже рекомендуемый способ передачи самих пропсов в React роутере.

Это очень просто. Если вы хотите увидеть весь пример, то проверьте его тут.

Редиректим роуты

В основном редиректы используют при проверке пользователей на авторизацию, если пользователь не авторизован, то его перенаправит на соответствующую страницу.

Вообще, всё это очень легко сделать с использованием компонента Redirect.

Давайте посмотрим на простой пример того, как он работает. Давайте создадим простую страницу, которая перенаправит на другую страницу основываясь на заданных условиях.

Давайте перенаправим с /old-route на /new-route

Простой пример редиректа

Добавляем роут редиректа в наш пример.

Сейчас мы создадим два URL.

/old-route это старый URL, с которого будет редирект на новый URL /new-route, используя компонент Redirect.

// Импортируем компонент Redirect из react-router
import { Route, Redirect } from 'react-router';

// Передаем параметры редиректа этому компоненту
<Redirect from="/old-route" to="/new-route" />
// Указываем параметры
<Route exact path="/new-route" component={RedirectPage} />

<Link to="/old-route">Redirecting to New page</Link>

// Page component
const RedirectPage = () => {
  return (
    <h3>Redirect Page</h3>
  );
};

Теперь проверьте пример простого редиректа

Тут вы увидите, что старая ссылка редиректит на новую ссылку и тут же показывает его в браузере.

Редирект посложнее, основанный на пропсах

Тут мы сделаем небольшую страничку, которая будет редиректить на дашборд, если пропс isLoggedin будет true или покажем простое сообщение в случае с false.

Давайте сначала сделаем страничку.

const AuthPage = ({ isLoggedIn }) => {
  if (isLoggedIn) {
    return <Redirect to="/dashboard" />;
  } else {
    return <h3>User not loggedin</h3>;
  }
};

const DashboardPage = () => {
  return <h3>Dashboard Page</h3>;
};

Давайте укажем роут для страниц Auth и Dashboard:

// Указываем роут для Dashboard
<Route exact path="/dashboard" component={DashboardPage} />
// Указываем роут для незалогиненной страницы, передав пропс isLoggedIn как false
<Route exact path="/auth-not-loggedin" render={(props) => <AuthPage {...props} isLoggedIn={false} />} />

<Route exact path="/auth-loggedin" render={(props) => <AuthPage {...props} isLoggedIn={true} />} />


// Lets add the links to navigate to these pages
<Link to="/auth-not-loggedin">Not Loggedin</Link>
<Link to="/auth-loggedin">User Loggedin</Link>

Теперь можете всё проверить сами. Роут редиректит и меняет URL в браузере на /dashboard, когда мы передаем isLoggedIn как true. И в свою очередь показывает простое сообщение для неавторизованных пользователей.

Посмотрите этот функционал тут

Вложенные роуты и контент

До этого мы видели простые примеры работы с реакт роутером. А сейчас мы разберемся с тем, как работать с вложенными роутами. Перед тем как углубиться в них давайте поймем, что же это такое?

Примеры вложенных роутов

Простой пример





www.example.com/users

Этот роут показывает всех пользователей.

Вложение первого уровня:

www.example.com/user/param
www.example.com/user/miguel

Эти примеры роутов показывают конкретного пользователя. Param и miguel это userId используемые для получения данных о конкретных пользователях.

Вложение второго уровня:

www.example.com/user/param/details
www.example.com/user/param/employer
www.example.com/user/miguel/details
www.example.com/user/miguel/employer

www.example.com/user/param/employer Этот роут получает простую информацию о пользователе и также информацию о его работодателе. Так что можно сказать, что это вложенные роуты. А вложения второго уровня уже зависят от параметров вложения первого уровня (userId: param)

Давайте начнем с конкретного примера. Мы собираемся показать пользователей и затем вывести детальную информацию о них.

Вложенные роуты в React Router

Для примера мы возьмем вот эти данные:

const users = [
  {
    name: 'Param',
    description:
      'Guy who writes lorem ipsum all the time when he needs content placeholder',
    tabs: [
      {
        name: 'personal',
        content: {
          firstname: 'Param',
          lastname: 'Harrison',
        },
      },
      {
        name: 'employer',
        content: {
          name: 'Jobbatical',
          city: 'Tallinn, Estonia',
        },
      },
    ],
  },
  {
    name: 'Miguel',
    description:
      'the best guy doing deployment in his own clusters of kubernetes world',
    tabs: [
      {
        name: 'personal',
        content: {
          firstname: 'Miguel',
          lastname: 'Medina',
        },
      },
      {
        name: 'employer',
        content: {
          name: 'Skype',
          city: 'Arizona, US',
        },
      },
      {
        name: 'other',
        content: {
          country: 'Mexico',
          age: 30,
        },
      },
    ],
  },
];

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

Используя эти данные мы создадим эти роуты:

Вложение первого уровня:

https://y5pt4.csb.app/user/Param
https://y5pt4.csb.app/user/Miguel

Вложения второго уровня:

https://y5pt4.csb.app/user/Param/tab/personal
https://y5pt4.csb.app/user/Param/tab/employer
https://y5pt4.csb.app/user/Miguel/tab/personal
https://y5pt4.csb.app/user/Miguel/tab/employer

Примеры

Показываем вложения первого уровня

import { BrowserRouter as Router, Link, Route } from 'react-router-dom';

function App() {
  return (
    <div className="App">
      <Router>
        <h3>Top level routes</h3>
        <ul className="unlist">
          {users.map((user, index) => {
            return (
              <li key={index}>
                <Link to={`/user/${user.name}`}>{user.name}</Link>
              </li>
            );
          })}
        </ul>
        <Route path="/user/:userName" component={UserPage} />
      </Router>
    </div>
  );
}

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

Также мы указываем схему роутов для пользователей. userName это параметр, передающийся компоненту UserPage.

const UserPage = ({ match }) => {
  
  const {
    params: { userName },
  } = match;
  const user = users.find(({ name }) => name === userName);

  
  return (
    <div>
      User Name: <strong>{user.name}</strong>
      <p>{user.description}</p>
    </div>
  );
};

Компонент UserPage покажет простую информацию о конкретном пользователе.

React роутер передаст пропсы match и мы сразу получим информацию о пользователе используя значение userName.

Показываем вложенные роуты как табы

const UserPage = ({ match }) => {
  ...

  return (
    <div>
      ...
      <p>Dyanmic nested route</p>
      <ul className="unlist">
        {user.tabs.map((tab, index) => {
          return (
            <li key={index}>
              <Link to={`${match.url}/tab/${tab.name}`}>{tab.name}</Link>
            </li>
          );
        })}
      </ul>
      
      <Route path={`${match.path}/tab/:tabName`} component={TabPage} />
    </div>
  );
};

Пропс match отдаёт нам url через match.url, который можно использовать для создания вложенных роутов.

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

match.path уже выдаёт нам имя пути. Его мы будем использовать для указания вложенной схемы роута.

Но зачем использовать match.path, а не match.url?

Потому что match.path содержит настоящий путь, то есть user/:userName, где match.url уже готовый url user/Param.

Именно поэтому match.url находится в Link, а match.path используется в определении роута.

<Route path={`${match.path}/tab/:tabName`} component={TabPage} />

tabName это параметр роута, который мы передаём компоненту TabPage. Он использует его для нахождения точного таба и показывает сам контент непосредственно в табе. Давайте глянем на код.

const TabPage = ({ match }) => {
  const {
    params: { userName, tabName },
  } = match;

  const tab = users
    .find(({ name }) => name === userName)
    .tabs.find(({ name }) => name === tabName);

  // Show the content for that particular tab
  return (
    <div>
      Tab Name: <strong>{tab.name}</strong>
      <h6>Tab content: </h6>
      <ul>
        {Object.keys(tab.content).map((key, index) => {
          return (
            <li key={index}>
              <span>{key} : </span>
              <strong>{tab.content[key]}</strong>
            </li>
          );
        })}
      </ul>
    </div>
  );
};

Пропсы match отдадут нам все params в URL userNametabName

Находим таб в наших данных, используя оба параметра

Показываем информацию таба

Следовательно, мы показали вложенные роуты и вложенный контент в этом примере.

Посмотрим пример в действии.

Пример

БОНУС: Используем <NavLink> для указания того, какой элемент активен в панели навигации

В React роутере есть <NavLink> и <Link> компоненты. Далее мы рассмотрим разницу между ними и то, как можно было бы использовать <NavLink> для указания того, какая именно ссылка активна в панели навигации приложения.

Оба компонента <NavLink> и <Link> могут быть использованы для создания удобной навигации в приложении. Внутри обоих компонентов мы можем использовать атрибут to для указания пути ведущему по ссылке. После установки react-router-dom и импорта <Link>, всё связанное с ним будет выглядеть так:

import { Link } from 'react-router-dom'

<Link to="/about">About</Link>

Вы можете также импортировать и <NavLink>:

import { NavLink } from 'react-router-dom'
<NavLink to="/about">About</NavLink>

Оба они компилируются в html код, как тут:

Может показаться, что основной их функционал одинаков. Так зачем же нам тогда оба? А затем, что <NavLink> идёт вместе с дополнительным атрибутом стилизации, который позволяет нам стилизовать по особенному именно активную ссылку.

Вот пример:

import React, { Component } from "react";
import { Link } from "react-router-dom";
class NavBar extends Component {
  render() {
    return (
      <div className="navbar">
        <Link to="/" className="navbar-logo">
          dA
        </Link>
        <div className="navbar-list">
          <Link to="/search">Collection</Link>
          <Link to="/map">Map</Link>
          <Link to="/login">Log In</Link>
        </div>
      </div>
    );
  }
}
export default NavBar;

Компонент NavBar даёт нам навигацию по четырём роутам — “/”, “/search”, “map” и “login” с использованием старого доброго компонента <Link>.

Как показано выше, мы можем вполне успешно прыгать по роутам, каждый из которых ассоциирован с компонентом <Link>.

А теперь давайте посмотрим на различия между двумя компонентами. <NavLink> даёт нам дополнительный атрибут activeClassName. В этом атрибуте мы можем указывать класс в CSS, который будет выделять то, что ссылка активна.

import React, { Component } from "react";
import { NavLink } from "react-router-dom";
class NavBar extends Component {
  render() {
    return (
      <div className="navbar">
        <NavLink to="/" className="navbar-logo">
          dA
        </NavLink>
        <div className="navbar-list">
          <NavLink to="/search" activeClassName="chosen">
            Collection
          </NavLink>
          <NavLink to="/map" activeClassName="chosen">
            Map
          </NavLink>
          <NavLink to="/login" activeClassName="chosen">
            Log In
          </NavLink>
        </div>
      </div>
    );
  }
}
export default NavBar;

CSS селекторы описанные ниже, рендерят линию подчеркивания в тех случаях, когда мы наводим на ссылку и когда она открыта и активна.

.navbar-list a.chosen,
.navbar-list a:hover {
  border-bottom: 1px solid #F14B31;
}

С помощью <NavLink>, мы можем добавить атрибуты стилизации для активных элементов, чей путь совпадает с URL в браузере.