React course
  • Компоненты и коллекции
  • TypeScript
  • Стилизация
  • События и состояния
  • Формы
  • Жизненный цикл
  • Функциональные vs классовые компоненты
  • Основы Redux
  • Redux Toolkit
  • Асинхронный Redux
  • Селекторы
  • React Router
  • Code splitting
  • Паттерны и контекст
  • Анимация
Powered by GitBook
On this page
  • 1. Маршрутизация
  • 1.1. Структура URL-строки
  • 1.2. История навигации
  • 2. React Router
  • 2.1. BrowserRouter
  • 2.2. Route
  • 2.3. Switch
  • 2.4. Redirect
  • 2.5. Link и NavLink
  • 2.6. Route props
  • 3. Строка запроса
  • 3.1. Извлечение параметров
  • 3.2. Изменение параметров
  • 3.3. Отслеживание изменений
  • 4. Редиректы
  • 4.1. Проп history
  • 4.2. Свойство location.state

Was this helpful?

React Router

PreviousСелекторыNextCode splitting

Last updated 4 years ago

Was this helpful?

1. Маршрутизация

Отличающее преимущество веб-приложения от десктоп это наличие URL, при переходе по которому, пользователь оказывается в определенной части приложения. Так можно сохранить закладку или передать ссылку другому пользователю, при этом ему будет отображен один и тот же интерфейс (за исключением приватных данных).

Маршрутизация (routing) - это не побочный эффект при написании приложения, наоборот, грамотную архитектуру навигации необходимо продумывать в первую очередь.

1.1. Структура URL-строки

Аналогией URL-строки может быть адрес по которому вы проживаете: улица, дом, квартира. У каждого состояния интерфейса должен быть свой адрес, свой URL. То, что видит пользователь, состояние интерфейса, должно быть описано в URL.

  • https:// - протокол

  • mysite.com/ - хост

  • books/e3q76gm9lzk - путь, то где мы находимся в приложении

  • e3q76gm9lzk - url-параметр. Параметры бывают динамическими или статическими

  • ? - символ начала строки запроса

  • ?category=adventure&status=unread​ - строка запроса

  • category=adventure - пара параметр=значение

  • & - символ "И", разделяет параметры строки запроса

  • #comments - якорь (хеш), определяет положение на странице

1.2. История навигации

История навигации - то как мы переходим ссылкам, как переходы хранятся и парсятся. От типа истории зависит метод ее хранения и изменения.

Существует несколько типов истории.

  • Browser history - использует HTML5 History API, стандарт управления историей браузера из JavaScript.

  • Hash history - в старых браузерах не поддерживается HTML5 History API, поэтому для них существует эта реализация.

  • Memory history - позволяет использовать историю сессии в памяти, вне окна браузера. К примеру для тестирования логики без интерфейса и в средах без DOM, к примеру React Native.

2. React Router

Предоставляет набор компонентов для управления частями URL-строки и отображения различных компонетов в зависимости от текущего ее состояния. Разбит на пакеты для различных платформ, нас интересует react-router-dom.

npm install react-router-dom

В React Router есть три типа компонентов: компонент маршрутизатора, компоненты согласования маршрутов и компоненты навигации.

2.1. BrowserRouter

В основе каждого одностраничного приложения стоит маршрутизатор. Компонент <BrowserRouter> создает раутер и объект history, чтобы синхронизировать интерфейс с URL-адресом. Используя контекст, передает данные о текущем URL всему поддереву компонентов.

import { BrowserRouter } from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.querySelector('#root'),
);

2.2. Route

Компонент позволяющий связать определенный URL и компонент для рендера. Его задача заключается в том, чтобы отобразить некоторый интерфес, когда location.pathname соответствует значению пропа path. Компонент <Route> можно использовать в любом месте где необходимо рендерить контент на основе текущего URL.

import React from 'react';
import { Route } from 'react-router-dom';

const App = () => (
  <>
    <Route path="/" exact component={Home} />
    <Route path="/about" component={About} />
    <Route path="/contact" component={Contact} />
  </>
);

Согласование маршрута выполняется путем сопоставления пропа path и текущего значения location.pathname. Если значение location.pathname начинается на указанный путь в path, <Route> отрендерит указанный компонент, в противном случае вернет null.

Когда location.pathname начинается на '/about', первый и третий рауты отрендерят null, а второй отрендерит компонент <About>.

  • Проп exact указывает на необходимость точного совпадения path и location.pathname

  • <Route> без указанного path всегда рендерит компонент

2.2.1. Проп component

Используется когда компонент необходимо отрендерить без передачи дополнительных пропсов. Стандартные пропсы match, location и history будут переданы компоненту автоматически.

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

// ✅ Хорошо
<Route path="/about" component={About} />

// ❌ Плохо
<Route
  path="/about"
  component={props => <About {...props} extraProp="amazing prop" />}
/>

2.2.2. Проп render

Позволяет использовать инлайн-функцию вместо компонента без нежелательного эффекта ре-рендера как в случае с component. Используется тогда, когда компоненту необходимо передать дополнительные пропсы, кроме тех что передает <Route>.

<Route
  path="/about"
  render={props => <About {...props} extraPropName="value" />}
/>

2.3. Switch

Группирует и отображает первый дочерний маршрут, path которого соответствует текущему location.pathname, игнорруя все последующие.

import React from 'react';
import { Switch, Route } from 'react-router-dom';

const App = () => (
  <div>
    <Switch>
      <Route path="/" exact component={Home} />
      <Route path="/about" component={About} />
      <Route path="/contact" component={Contact} />
      <Route component={NotFound} />
    </Switch>
  </div>
);

2.4. Redirect

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

import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';

const App = () => (
  <div>
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/contact" component={Contact} />
      <Redirect to="/" />
    </Switch>
  </div>
);
  • По умолчанию подменяет текущую запись в истории, для того чтобы добавить новую запись на верх стека можно передать проп push.

  • Вместо строки, проп to может принимать полноценный объект формата location.

2.5. Link и NavLink

Для создания навигации нельзя использовать обычный HTML-тег <a href="/about">. При клике, вместо того чтобы изменить URL на текущей странице, и позволить раутеру выполнить навигацию на стороне клиента, браузер выполнит GET-запрос и обновит страницу, а это не то, что нам нужно.

Компоненты <Link> и <NavLink> используются для создания ссылок. Они рендерят HTML-тег <a>, но с расширенным функционалом клика, используя возможности объекта history по замене URL.

<Link to="/books">Books</Link>

<Link to="/books?category=adventure#treasure-island">Adventure books</Link>

Проп to можно передавать в виде строки описывающей href будущей ссылки, или как объект location со следующими (необязательными) свойствами:

  • pathname - строка, путь для ссылки.

  • search - строковое представление параметров запроса.

  • hash - хэш для добавления в конец URL.

  • state - объект, который будет записан в location.state после перехода по ссылке.

<Link
  to={{
    pathname: '/books',
    search: '?category=adventure',
    hash: '#treasure-island',
    state: { from: '/dashboard' },
  }}
/>

Компонент <NavLink> отличается от <Link> только тем, что может иметь дополнительные стили, если текущий URL совпадает со значением пропа to.

  • activeClassName - строка классов для объеденения с className когда элемент активен.

  • activeStyle - объект инлайн стилей для добавления к элементу когда он активен.

  • exact - когда true, активные классы/стили будут применяться только в том случае, если местоположение точно совпадает со значением пропа to.

<NavLink to="/books" className="link" activeClassName="active-link">
  Books
</NavLink>

2.6. Route props

Компоненту отрендеренному через <Route> будет передано несколько специальных пропсов хранящих много полезной информации.

  • match — объект с информацией о том как совпали path и location.pathname

  • location - объект хранящий информацию о текущем URL

  • history - объект истории, созданный самим раутером

2.6.1. Проп match

Объект, хранит информацию о том, как path совпал с location.pathname. Содержит следующие свойства.

  • params - объект пар ключ:значение, соответствующих динамическим параметрам URL.

  • isExact - указыват на точное соотвествие path и location.pathname.

  • path - паттерн пути на который замачился <Route>. Используется для создания вложенных маршрутов.

  • url - совпавшая часть URL-адреса. Используется для создания вложенной навигации.

2.6.2. Проп location

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

{
  key: 'ac3df4',
  pathname: '/books',
  search: '?sortby=latest',
  hash: '#comments',
  state: {
    from: '/login'
  }
}

2.6.3. Проп history

Объект истории со свойствами и методами для программной навигации. Используется для перенаправлений.

  • history.push(path [, state]) - добавляет новую запись на стек записей истории.

  • history.replace(path [, state]) - подменяет текущую запись на новую на стеке записей истории.

3. Строка запроса

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

https://app.com/articles

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

https://app.com/articles?category=health&sortBy=latest

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

Текущее значение строки запроса хранится в пропе location.

{
  key: 'ac3df4',
  pathname: '/articles',
  search: '?category=health&sortBy=latest',
  hash: ''
}

3.1. Извлечение параметров

import queryString from 'query-string';

const queryParams = queryString.parse(props.location.search);

3.2. Изменение параметров

Допустим, для выбора категории статей используется <select>. При выборе опции необходимо обновлять URL используя метод history.push() для добавления новой записи в журнал истории.

Берем текущее значение location.pathname и обновляем search.

onCategoryChange = category => {
  this.props.history.push({
    pathname: this.props.location.pathname,
    search: `category=${category}`,
  });
};

3.3. Отслеживание изменений

Если меняется строка запроса, компоненту пробрасываются новые пропсы, и в методе componentDidUpdate() можно проверить изменилась ли категория и порядок сортировки. Если изменились, делаем HTTP-запрос или сортируем текущие статьи.

const getCategoryFromProps = props => queryString.parse(props.location.search).category;

componentDidUpdate(prevProps) {
  const prevCategory = getCategoryFromProps(prevProps);
  const nextCategory = getCategoryFromProps(this.props);

  if (prevCategory !== nextCategory) {
    this.fetchArticles(nextCategory);
  }
}

4. Редиректы

Перенаправления это удобный инструмент навигации пользователя между маршрутами приложения. Для их реализации используется компонент <Redirect> или методы history.push() и history.replace().

4.1. Проп history

Важно знать раличие методов push и replace.

  • history.push() - добавит новую запись в журнал истории, пользователь может вернуться на тот маршрут с которого пришел.

  • history.replace() - перезапишет текущую запись в журнале истории затерев текущую, пользователь не сможет вернутся на маршрут с которого пришел.

Продолжим работать со статьями. При рендере раута /articles есть категории статей, но при первом рендере маршрута еще нет строки запроса с параметром category. Именно поэтому необходимо сделать редирект, при этом перезаписав текущую страницу истории, чтобы пользователь не мог вернуться на страницу без параметров запроса.

componentDidMount() {
  /*
  * При маунте компонента проверяем наличие параметра `category` в строке запроса.
  */
  const category = getCategoryFromProps(this.props);

  /*
  * Если параметра нет, делаем `history.replace` с текущим маршрутом,
  * но добавляем категорию `all`, чтобы забрать все статьи с бекенда.
  * При этом перезаписываем текущую страницу истории.
  */
  if (!category) {
    this.props.history.replace({
      pathname: this.props.location.pathname,
      search: `category=all`,
    });

    return;
  }

  /*
  * Если при рендере такой параметр уже есть, например пользователь зашел по
  * сохраненной ссылке, делаем HTTP-запрос за статьями.
  */
  fetchArticlesByCategory(category).then(articles =>
    this.setState({ articles }),
  );
}

4.2. Свойство location.state

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

https://app.com/articles/:articleId

Тогда, если пользователь пришел с https://app.com/articles?category=sports, и захочет вернуться на сраницу всех статей, нажав стрелку "Обратно", он попадет куда надо. Но что если необходимо реализовать кнопку "Обратно к статьям" в интерфейсе приложения, как узнать откуда мы пришли?

Каждую статью сделаем ссылкой, добавив в свойство state информацию о текущем маршруте.

<Link
  to={{
    pathname: `/articles/${id}`,
    state: { from: this.props.location },
  }}
>
  Article title
</Link>

Тогда в компоненте статьи можем получить эту информацию и при клике на кнопку "Обратно к статьям" перенаправить пользователя на предыдущий маршрут.

handleGoBack = () => {
  this.props.history.push(this.props.location.state.from);
};

Нужно учитывать ситуацию когда пользователь перешел на страницу статьи по сохраненный ссылке, и в текущей сессии не был на странице всех статей. В свойстве location.state не будет объекта, и попытка доступа к state.from приведет к ошибке выполнения скрипта.

Поэтому необходимо проверить location.state. Если state есть - перенаправляем пользователя туда, откуда он пришел. Если state нет - перенаправляем на /articles, при этом параметр category назначаем по свойству статьи (если в объекте статьи есть такое свойство).

handleGoBack = () => {
  const { state } = this.props.location;
  const { category } = this.state;

  if (state) {
    this.props.history.push(state.from);
    return;
  }

  this.props.history.push({
    pathname: '/articles',
    search: `?category=${category}`,
  });
};

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

Чтобы получить значения параметров, можно использовать возможности нативного класса , а также библиотеки или . Любой метод позволит сделать парс строки запроса и получить объект с парами ключ:значение.

HTML5 History API
history
React Router
Документация React Router
Документация BrowserRouter
Документация Route
Документация Redirect
Документация Link
Документация NavLink
Документация Route props
Документация location
URLSearchParams
qs
query-string