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

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

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

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

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

React это широко используемая библиотека для client-side веб приложений. Сами знаете, в любом веб приложении будет по несколько страниц. Правильный роутинг ссылок и загрузка разных страниц в зависимости от параметров заданных маршрутов это главное требование для буквально любого приложения.

Существует отличный npm пакет, который берет на себя все сложности роутинга на React. Это react-router-dom — одна из самых используемых библиотек в react.

Простой роутинг

Давайте создадим две простые страницы.

Главную (/)

И страницу About (/about)

Создайте простое react приложение с использованием create-react-app CLI. Это очень просто сделать с помощью npx — npx create-react-app my-react-app

// App.js
import React from 'react';

const App = () => {
  return (
    <section className="App">
      <h1>React routing Example</h1>
    </section>
  );
};

export default App;

Давайте создадим две страницы. Ну или просто два функциональных компонента.

// App.js
...

const IndexPage = () => {
  return (
    <h3>Home Page</h3>
  );
};

const AboutPage = () => {
  return (
    <h3>About Page</h3>
  );
};

...

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

Ссылки для навигации между страницами.

Нам нужно будет определить пути к страницам. Тут мы укажем путь URL и компонент, который нужно загружать с этим URL.

Ещё мы определим роутер, который будет проверять наличие запрашиваемого URL в указанных роутах.

Итак, давайте создадим компоненты Link и Route. Для начала установите пакет yarn add react-router-dom.

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

const App = () => {
  return (
    <section className="App">
      <Router>        
       <Link to="/">Home</Link>        
       <Link to="/about">About</Link>        
       <Route path="/" component={IndexPage} />        
       <Route path="/about" component={AboutPage} />      
      </Router>    
    </section>
  );
};

А теперь дайте пройдем по каждой строчке отдельно.

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

Тут мы импортируем три компонента,

Link, который создаст HTML ссылку на страницы.

Route, который укажет маршруты.

И Router в котором будет храниться вся логика нашего роутинга. Когда пользователь кликает на ссылку, этот компонент проверяет есть ли она в указанных роутах. Если да, то роутер сменит URL в браузере и роут отрендерит заданный компонент.

// Link with URL
<Router>
  <Link to="/">Home</Link>
  <Link to="/about">About</Link>
</Router>

Router должен быть родительским компонентом для Link и Route. Собственно так он и управляет роутингом. Если мы поставим Link или Route за его пределы, то тут мы получим ошибку.

Зачем нам нужен Link, а не HTML анкор с href?

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

В то время как Link проверяет роутер и роутер проверяет наличие роута, загружая компонент без перезагрузки страницы. Поэтому это и называется client side роутингом. Он не подгружает страницу с сервера при обращении к компоненту Link.

// Route with definition
<Route path="/" component={IndexPage} />

Тут у Route есть путь и props компоненты. component помогает отрендерить компонент, когда пользователь заходит на этот роут. path props указывает путь url, который должен совпадать при посещении пользователем страницы.

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

Кликнув на ссылку about, она отрендерит и IndexPage, и AboutPage. Но почему?

Потому что путь указанный для About это /about. Роутер же в свою очередь пробегается по всем определением роутов, сверху вниз. Сначала он проверяет роут с путем /, а у About в URL есть /, так что сначала он рендерит IndexPage. А уже потом проверяет следующий роут /about, который тоже совпадает с заданными параметрами, что приводит к рендерингу компонента AboutPage.

Как точно попасть на нужный роут?

Всё очень просто. Используйте props exact в Route.

...

const App = () => {
  return (
    <section className="App">
      <Router>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Route exact path="/" component={IndexPage} />        
        <Route exact path="/about" component={AboutPage} />                         
</Router>
    </section> 
  );
};

...

Этот prop поможет попасть на нужный роут только при полном совпадении, а если нет, то он просто не будет рендерить компонент.

Теперь оба компонента будут отлично рендериться и Link будет работать правильно.

Какие бывают роутеры

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

Но есть ещё несколько других типов роутеров в react router. Сейчас мы посмотрим когда их использовать.

MemoryRouter

// https://example.com (Один и тот же url для всех роутов)
import { MemoryRouter as Router } from ‘react-router-dom’;

Это роутер, который не меняет URL в браузере, а оставляет изменения в памяти.

Он очень полезен для тестирования и вне браузерных сред разработки.

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

HashRouter

/*
Роуты с хешем, тут вы можете обращаться к истории.
https://example.com/#/
https://example.com/#/about
*/
import { HashRouter as Router } from 'react-router-dom';

Это роутер, который использует client Side hash роутинг.

Всякий раз, когда рендерится новый роут, он обновляет URL в браузере с хешированными роутами. (/#/about)

Хэш часть ссылки не будет обработана сервером, сервер будет всегда отправлять index.html для каждого запроса и игнорировать хешированное значение, которое будет обработано уже React роутером.

Он используется для Legacy браузеров, которые обычно не поддерживают pushState API.

Ему не нужна конфигурация на сервере для обработки роутов.

Этот роут не рекомендован командой разработчиков которая создавала сам пакет react router. Используйте его только в том случае, если вам нужна поддержка Legacy браузеров или при осутствии серверной логики для обработки client Side роутов.

BrowserRouter

/*
Вы можете использовать историю
https://example.com/
https://example.com/about
*/
import { BrowserRouter as Router } from 'react-router-dom';

Самый популярный роутер и роутер для современных браузеров, который использует HTML5 pushState (pushStatereplaceState и popState API)

Он роутится как обычный URL в браузере, так что вы не сможете по ссылке отличить был ли это server Side рендеринг или же client side.

Тут подразумевается то, что ваш сервер обрабатывает все URL запросы (//about) и указывает на корневой index.html. И уже отсюда, BrowserRouter обрабатывает роутинг релевантной страницы.

Он принимает forceRefresh пропсы для поддержки Legacy браузеров, которые не тянут HTML5 pushState API.

Динамические страницы в React Router

В начале статьи мы изучили то, как создавать простые статические страницы в роутере. Сейчас мы узнаем как создавать динамические ссылки в react роутере.

Мы создадим два роута,

Роут Users — статические роуты для отображения всех ссылок для отдельных пользователей.

Роут User — каждый пользователь будет идентифицирован по своему уникальному ID и ссылка будет передавать этот ID, а компонент будет отображать нужный контент пользователя.

Давайте создадим простой массив users с данными пользователей.

const users = [
  {
    name: `Param`,
  },
  {
    name: `Vennila`,
  },
  {
    name: `Afrin`,
  },
];

Давайте создадим новые роуты для всех пользователей и одного пользователя в нашем App.js файле.

// App.js
...

const UsersPage = () => {  return (    <h3>Users Page</h3>  );};
const App = () => {
  return (
    <section className="App">
      <Router>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users">Users</Link>        
        <Route exact path="/" component={IndexPage} />
        <Route exact path="/users" component={UsersPage} /> 
        <Route exact path="/about" component={AboutPage} />
      </Router>
      <a href="/about">about with browser reload</a>
    </section>
  );
};

...

Мы создали ссылку на страницу пользователей и также определили роут для страницы пользователей вместе с ассоциированным компонентом (UsersPage).

Давайте создадим ссылки для каждого из них с UsersPage. (/user/1/user/2/user/3)

// userId будет индекс в массиве + 1
const UsersPage = () => {
  return (
    <>
      <h3>Users Page</h3>
      {users.map((user, index) => (
        <h5 key={index}>
          <Link to={`/user/${index + 1}`}>{user.name}'s Page</Link>
        </h5>
      ))}
    </>
  );
};

Итак, мы создали страницу пользователей с ссылками на них. Если вы кликните на ссылку, то она приведет вас на несуществующую страницу, потому что мы не создали роут каждому из пользователей.

Мы можем создать каждый отдельный роут как тут:

<Route exact path="/user/1" component={UserPage1} />
<Route exact path="/user/2" component={UserPage2} />

Конечно же нет! Я лгу вам, все мы знаем, что это не подходит для динамических страниц с большим количеством данных. Давайте посмотрим на то, как мы можем создать динамические роуты в React. Это очень и очень легко.

<Route path="/user/:userId" component={UserPage} />

Тут :userId это динамические параметры роута. Он передается компоненту. Вы легко можете получить доступ к пропсам userId в компоненте UserPage.

Давайте добавим этот код в наш пример.

// App.js
...

const UserPage = () => {  return (    <h3>User Page</h3>  );};
const App = () => {
  return (
    <section className="App">
      <Router>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users">Users</Link>
        <Route exact path="/" component={IndexPage} />
        <Route exact path="/users" component={UsersPage} />
        <Route exact path="/user/:userId" component={UserPage} /> 
        <Route exact path="/about" component={AboutPage} />
      </Router>
      <a href="/about">about with browser reload</a>
    </section>
  );
};

...

Теперь наша страница пользователя работает. Но она не показывает никакую информацию непосредственно о самом пользователе. Давайте с этим разберемся.

Как получить доступ к параметрам роута в компоненте

React роутер передаёт два пропса всем компонентам. Это match и location.

Давайте посмотрим, какая информация находится в этих пропсах, выведя их через сам компонент.

// App.js

const UserPage = ({ match, location }) => {  return (
    <>
      <p>
        <strong>Match Props: </strong>
        <code>{JSON.stringify(match, null, 2)}</code>      
      </p>
      <p>
        <strong>Location Props: </strong>
        <code>{JSON.stringify(location, null, 2)}</code>      
      </p>
    </>
  );
};

И что же там внутри:

/*
  URL: /user/1
  userId: 1
*/

// Match Props
{ "path": "/user/:userId", "url": "/user/1", "isExact": true, "params": { "userId": "1" } }

// Location Props
{ "pathname": "/user/1", "search": "", "hash": "", "key": "7e6lx5" }

Если мы взглянем на контент, то интересный нам параметр userId находится в match.params.userId.

Давайте применим параметры в UserPage компоненте и покажем информацию о пользователе:

// Получаем userId из пропса match и показываем конкретного пользователя из массива пользователей
const UserPage = ({ match, location }) => {
  const { params: { userId } } = match;

  return (
    <>
      <p>
        <strong>User ID: </strong>
        {userId}
      </p>
      <p>
        <strong>User Name: </strong>
        {users[userId - 1].name}
      </p>
    </>
  );
};
// Деструктуризация объекта in JavaScript
const {
  params: { userId },
} = match;