#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.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
United Kindom
15 дней назад
Здраствуйте.Такои вопрос сделал проиект все работает , но паботает только на url: localhost:3000 тоесть только для разработки с командой npm start, но как сделать для продакшина чтоб я запустил только index.html а там уже все работало. Вот есль что сылка на мой пройект ------ https://drive.google.com/open?id=1JzVB9A6LMnOuseHhEAEhgQt24y6iKp0T ----- можете ответить на мой email: chizimcezar@usa.com ну или здесь.
monsterlessons
14 дней назад
Добрый день. Для того чтобы запускать это в продакшене вам необходим сервер, который будет рендерить ваш html файл в который вы подключаете весь js.
United Kindom
14 дней назад
А какой сервер подойдет для такой задачь? и почему при другом случее когда я использую react & redux без create-react-app build запускается без сервера просто с файла index.html куда весь js уже скомпилирован и подключен?
monsterlessons
11 дней назад
Для того, чтобы в продакшене весь код минифицируется и сжимается. Это кардинально отличается от локальной разработки. Почитайте статьи как залить сайт в продакшен, настроить сервер и тп. В интернете много быстрых решений как Heroku, Netlify, Now и другие.
Jin zakk
11 месяцев назад
Добрый день, благодарю за курс по Redux. Недавно набрел на статью на хабре (https://habrahabr.ru/post/350850/) в которой затрагивалась тема использования Redux. Часто ли вы используйте Redux, когда пишете проект на React?
monsterlessons
11 месяцев назад
Добрый день. Я использую 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
2 лет назад
Честно говоря я не понял зачем использовать 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
2 лет назад
"Если не знаешь нужно ли тебе это, то не используй" отличное правило. Основная идея react-router-redux в том, что оно синхронизирует react-router с redux. Без него вы не можете делать time travel в redux-devtools с учетом url. То есть при настроеном react-router-redux, когда вы будете делать time travel по екшенам, события будут передаваться в react-router и менять страницы тоже соответственно. Вы можете включать, выключать екшены в дереве и страницы будут меняться. Ну и переходы по страницам вы сможете делать в redux way с помощью dispatch(push('/')).
winlx
2 лет назад
Спасибо! Я так и думал что нужно для time travel )). Нужно больше практики как обычно и тогда всё встанет на свои места.
winlx
2 лет назад
Чуть не пропустил этот урок, если зайти https://monsterlessons.com/project/categories/redux - все 9 уроков видны, а если сюда https://monsterlessons.com/project/series/redux-js-dlya-nachinayushih - 8 уроков, и с 8-го урока нет ссылки перехода на 9-й.
monsterlessons
2 лет назад
Это потому, что урок находится в категории redux, но не включен в серию "Redux для начинающих", так как по моему мнению не относится к базовым знаниям.
Aleksandr Polyakh
2 лет назад
Доброе время суток. Скажите пожалуйста, что вы думает по поводу связки Redux + immutable.js ?
monsterlessons
2 лет назад
Я очень сильно не люблю 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 заменить, так код легче читается