#9 ReduxJS - роутинг с помощью react-router-redux

11:34
poster
В этом уроке мы рассмотрим, как использовать роутер с redux, используя библиотеку react-router-redux.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

В прошлых уроках мы научились с вами использовать Redux для работы с состоянием приложения и React-router для роутинга. Это все хорошо, но они не работают вместе. Мы же хотим, чтобы роутер был частью redux store и мы могли пользоваться его данными и экшенами в обычном для redux way. В этом нам поможет библиотека react-router-redux. На сегодняшний день - это самая популярная библиотека для роутинга в redux.

Для начала нам нужно установить библиотеку

npm install --save react-router-redux

Теперь мы хотим добавить reducer из этой библиотеки в наши редьюсеры. В файл index.js в редьюсерах импортируем routerReducer

import { routerReducer } from 'react-router-redux';

export default combineReducers({
  routing: routerReducer,
  tracks,
  playlists,
  filterTracks
});

и добавляем этот редьюсер с названием routing.

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

Для этого в index.js импортируем функцию syncHistoryWithStore, создадим новую переменную history. и в роутер передадим history как параметр.

import { syncHistoryWithStore } from 'react-router-redux';
const history = syncHistoryWithStore(hashHistory, store);

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}/>
      <Router path="/about" component={About}/>
    </Router>
  </Provider>,
  document.getElementById('root')
);

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

Как же теперь нам в нашей компоненте App подписаться, чтобы иметь доступ к данным роута? Очень просто. У нас есть в методе mapStateToProps второй параметр ownProps. Давайте на него посмотрим.

(state, ownProps) => ({
  tracks: state.tracks.filter(track => track.name.includes(state.filterTracks)),
  ownProps
}),

Как мы видим, ownProps содержит много информации о route. Давайте пройдемся по самым часто используемым полям. Самые часто используемые - это params и location. Чтобы понять что оно дает, давайте создадим страницу отдельного трека. Мы делаем это, чтобы попробовать применить params из ownProps.

Добавим в роутер новый роут, в котором будет динамический параметр id.

<Route path="/tracks/:id" component={Track}/>

и компонент Track

import React from 'react';

const Track = () => <div>Track</div>;

export default Track;

Теперь давайте добавим 1 трек, который будет у нас в редьюсере всегда, так как наши треки постоянно пустые при перезагрузке.

const initialState = [
  {
    id: 2334,
    name: 'Super track'
  }
];

Зайдя на нее мы хотим видеть название трека. Для этого давайте добавим connect в компонента Track.

import React from 'react';
import { connect } from 'react-redux';

const Track = ({ track }) => <div>{track.name}</div>;

const mapStateToProps = (state, ownProps) => ({
  track: state.tracks.find(track => track.id === Number(ownProps.params.id))
})

export default connect(mapStateToProps)(Track);

Мы добавили connect и в mapStateToProps находим из списка треков в store. Единственный нюанс тут - это что из ownProps мы получаем параметры строкой, а id у нас у треков Number. Поэтому мы должны при поиске приводить id в Number.

Если мы посмотрим в браузер, то у нас вывелось название трека. Мы можем добавить в App ссылку на эту страницу для каждого трека, но нужно помнить, что при перезагрузке страницы оно будет выдавать ошибку, так как store очищается.

Импортируем Link из react-router и обернем имя трека в Link.

import { Link } from 'react-router';

<li key={index}>
  <Link to={`/tracks/${track.id}`}>{track.name}</Link>
</li>

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

В этом уроке мы рассмотрели как подключать react-router-redux и использовать роутер с redux. Мы реализовали получение трека используя параметры урла из react-router. Теперь вы можете строить приложения отслеживая всю информацию из роутера прямо в вашем redux store.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Jin zakk
9 месяцев назад
Добрый день, благодарю за курс по Redux. Недавно набрел на статью на хабре (https://habrahabr.ru/post/350850/) в которой затрагивалась тема использования Redux. Часто ли вы используйте Redux, когда пишете проект на React?
monsterlessons
9 месяцев назад
Добрый день. Я использую Redux всегда, вне зависимости от фреймворка. С реакт - это Redux, с Angular 5 - ngRx, с Vue - Vuex.
AndrewB
1год назад назад
Привет! А как подключить react-router-redux если я использую react-router 4?
monsterlessons
1год назад назад
Привет. Нужно установить react-router-redux@next, который поддерживает react-router 4, так как по умолчанию react-router-redux ставится для 3тьей версии. npm install --save react-router-redux@next npm install --save history Вот документация, как это делать. Инициализация практически не отличается. https://github.com/ReactTraining/react-router/tree/master/packages/react-router-redux
winlx
1год назад назад
Честно говоря я не понял зачем использовать react-router-redux. Я сначала всё сделал на react-router 4 без react-router-redux, всё работало также, только в состояние не записывался routing. Затем добавил react-router-redux 5 alpha (сделал по докам из git), всё по прежнему работало без изменений, только теперь в состояние еще записывался routing. Так для чего всё же нужен react-router-redux? Как можно использовать react-router-redux на практике или исходить из правила Redux "Если не знаешь нужно ли тебе это, то не используй"?
monsterlessons
1год назад назад
"Если не знаешь нужно ли тебе это, то не используй" отличное правило. Основная идея react-router-redux в том, что оно синхронизирует react-router с redux. Без него вы не можете делать time travel в redux-devtools с учетом url. То есть при настроеном react-router-redux, когда вы будете делать time travel по екшенам, события будут передаваться в react-router и менять страницы тоже соответственно. Вы можете включать, выключать екшены в дереве и страницы будут меняться. Ну и переходы по страницам вы сможете делать в redux way с помощью dispatch(push('/')).
winlx
1год назад назад
Спасибо! Я так и думал что нужно для time travel )). Нужно больше практики как обычно и тогда всё встанет на свои места.
winlx
1год назад назад
Чуть не пропустил этот урок, если зайти https://monsterlessons.com/project/categories/redux - все 9 уроков видны, а если сюда https://monsterlessons.com/project/series/redux-js-dlya-nachinayushih - 8 уроков, и с 8-го урока нет ссылки перехода на 9-й.
monsterlessons
1год назад назад
Это потому, что урок находится в категории redux, но не включен в серию "Redux для начинающих", так как по моему мнению не относится к базовым знаниям.
Aleksandr Polyakh
1год назад назад
Доброе время суток. Скажите пожалуйста, что вы думает по поводу связки Redux + immutable.js ?
monsterlessons
1год назад назад
Я очень сильно не люблю immutable.js. На волне хайпа по поводу immutable я пробовал его в одном проекте. Из основных минусов для меня это отвратительная документация, очень малое количество методов, код выглядит совсем не лаконично. Я согласен с тем, что иммутабельность - это хорошо, но не таким способом. Я предпочитаю использовать Ramda. Все ее методы не мутируют данные и все они каррированы. Это позволяет писать хороший, лаконичный код и легко его переиспользовать. Сейчас я снимаю видео о Ramda. Можете посмотреть здесь. https://monsterlessons.com/project/lessons/funkcionalnyj-javascript-s-ramda https://monsterlessons.com/project/lessons/kompoziciya-v-javascript-i-ramda
Иван Сусанин
2 лет назад
Самый долгий урок))) В общем ownProps в React компонентах у мну не появилось, зато в props оказались параметры типа location, params etc. а вот в connect уже ownProps как в видео. В общем если нет дочернего компонента - надо консолить на уровень выше, пока не поймешь че тебе пришло)) И да, переставил пока react-router на 3 версию. Автору спасибо за подробную инфу.
monsterlessons
2 лет назад
Спасибо. Если что в исходном коде урока залочены все версии библиотек и можно быть уверенным, что все будет работать как в уроке.
vlad
2 лет назад
День добрый Я недавно начал изучать React и Redux. И после просмотра Ваших уроков у меня получилась каша в голове. Я так думаю это произошло из-за того что в начале я изучал React, а затем Redux. И в процессе изучения функционал Redux перезатирал (перемешивался) функционал React. Можно тезисно (на пальцах и на примерах кода) сформулировать основные принципы взаимодействия React и Redux. И если можно сделать это на примере сайта-блога на MySql. Т.е. например: 1. Получаем данные из MySql - массив страниц сайта (динамическое многоуровневое меню), массив категорий статей (многоуровневой аккордеон), массив статей - я так понимаю это реализуется через Ajax? Можно пример кода? 2. Выводим данные на экран - меню, аккордеон, статьи - можно поподробнее этот момент, с примером кода? 3. Вешаем на элементы события - я так понимаю это нужно делать в редюскомбайне? можно с примером кода? 4. Основные принципы структурирования папок и файлов сайта? 5. Можно тоже на пальцах разъяснить принципы создания "автономных компонентов"? На сколько я понял такой компонент можно использовать в нескольких местах сайта. 5.1. Основные требования при создание такого компонента в родительских и в родительско-родительских компонентах?
monsterlessons
2 лет назад
Реакт - это только view слой. Redux - работа с данными. 1. У нас есть реакт компонент, который рендерит меню. В componentDidMount мы можем вызвать action из redux fetchMenu(). У внутри этого екшена мы получим от API данные и положим в store. Тогда в нашем реакт компоненте мы можем подписаться на данные из store и отрендерить их. 2. С помощью connect, как я показывал в этом уроке http://monsterlessons.com/project/lessons/redux-js-connect-v-react-redux 3. Событие вешаются в React и если мы говорим о Redux, то функцией может быть екшен например <div onClick={fetchData} /> 4. Самое простое containers - компоненты страниц сайта components - компоненты на страницах либо компоненты, которые можно переиспользовать index - старт приложения + роутер/либо роутер в отдельный файл если большой. 5. Например можно создать стилизированную кнопку, куда мы передаем функцию того, что будет происходить на клик и тип кнопки (info, success, error) <Button type='success' action={saveData} /> 5.1 Требования только передавать то, что ожидает компонент например тип и функцию. Скоро выйдет серия "Создание магазина на React/Redux". Я думаю она ответит на все ваши вопросы.
Ezheg
2 лет назад
Подскажите, пожалуйста. Пишу компонент Search. Он представляет собой input, вводимое значение сохраняется в локальном state (для отображения в инпуте) и в роутере в query (через throttle 300мс). Другие компоненты смотрят этот query и выполняют необходимую фильтрацию элементов. Redux здесь не использую, т.к. нагуглил советы что лучше использовать что то одно: или redux, или роутер. Получается что значение меняется только от ввода в инпут. Как мне сделать дополнительно прослушку query роутера и реагировать на это изменение? При том что это дочерняя компонента и я могу юзать withRouter.
monsterlessons
2 лет назад
Локальный стейт на то и локальный вы его никак не шарнете. По факту, в вашем примере query роутера является у вас хранилищем. Все компоненты, которые должны слушать query подписывайте с помощью withRouter. И соответсвенно в this.props получите текущий query. Также использование react-router + redux это очень популярная практика и отлично работает. Не знаю, где это пишут, что их нужно использовать только раздельно.
Ezheg
2 лет назад
Про редакс или роутер я не верно выразился. Имелось ввиду где хранить значение: в сторе редакса, или в урле. Мне нужно чтобы значение было в урле (?search=test), чтобы человек мог подулиться этим урлом с другими. Соответственно дублировать это же значение в редакс не имеет смысла. Верно? Теперь подробнее то с какой я столкнулся проблемой. Вот код Search: http://pastebin.com/1u4DhGDr Допустим в другом компоненте я выполняю код: this.props.router.replace({ ...this.props.location, query: { sort: item.name } }); Если до этого query был таким { search: 'test' }, то после станет таким { sort: 'popular' }. В текущей реализации компонент Search не видит это изменение. Я пробовал сначала не использовать state, и напрямую смотреть this.props.location.query.search, но реакт ругается: Warning: Search is changing a controlled input of type search to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components
monsterlessons
2 лет назад
Дублировать значение не имеет смысла. Хранить в урле нормально. Вам нужно в props Search пробрасывать новые данные из роутера. Я это делаю обычно так export const mapStateToProps = (state, { location: { query } }) => { return { searchValue: query.search } } export default compose( withRouter, connect(mapStateToProps) )(Search) Но это если вы используете redux и redux-connect. Тогда в Search вы можете в componentWillReceiveProps менять локальное состояние при изменении props.
Ezheg
2 лет назад
ownProps содержит данные о роутере (3:37), но надо было бы сказать по другому. ownProps - это параметры текущего компонента, и компонент Route передает в этот компонент свои данные. И в связи с этим данные роутинга будут только у компонента, что указан в Route. Во все дочерние компоненты эти даные надо передавать через параметры. Или использовать контекст. Или ещё что то, но т.к. я с реактом всего неделю не знаю что ещё :) Дальше вы разбираете как работать с этими данными из параметров, но, к сожалению, это никак не связано с Redux. По сути про связку роутера и Redux было сказано как установить и какой есть ивент роутера в Redux. Вы не используете store для получания инфы о роутинге. Нет примера с ивентом роутера. Ещё можно было бы упомянуть как делать переход на другую страницу без компонента Link, и может ли react-router-redux это упростить. Ощущение что от темы ушли. К Redux подключили, а зачем - не понятно.
monsterlessons
2 лет назад
Это был базовый пример. Естественно, что тема более обширна чем видео на 10 минут. Store и не нужно использовать для получения инфы о роутинге. Либо ownProps, если компонента является чайлдом роута, либо withRouter, если это дочерняя компонента.
Ezheg
2 лет назад
За withRouter спасибо! И за все видео, тоже, огромное спасибо ;)
monsterlessons
2 лет назад
На здоровье)
Hambat Gurbanov
2 лет назад
Привет, можешь помочь пожалуйста Я с помощью Axios пытаюсь подключиться к серверу и получить данные import axios from 'axios'; axios.get('http://ellenflare.com/loads/json') .then(function (res) { const initialState = res.data; export default function tracks(state = initialState, action) { if (action.type === 'ADD_TRACK') { return [ ...state, action.payload ]; } else if (action.type === 'FETCH_TRACKS_SUCCESS') { return action.payload; } return state; } }); при експорте возникает ошибка Module build failed: SyntaxError: 'import' and 'export' may only appear at the top level (9:2) если не сложно подскажи пожалуйста как правильно экспортировать функцию в моем случае? за ранее спасибо
monsterlessons
2 лет назад
Оно говорить что экспорт можно писать только на верхнем уровне. Его нельзя вкладывать в then. функция tracks должна быть отдельно. Нужно либо вынести ее в отдельный файл и передавать в createStore первым параметром res.data, а вторым параметром функцию tracks. Либо получать данные в екшене уже после старта приложения, так как обычно в initialState приложения выносятся только данные с localstorage.
Иван Сусанин
2 лет назад
Если правильно понял курс - это тот случай когда нужно использовать/написать middleware. Да и if/else лучше на switch заменить, так код легче читается