Использование с React Router
Итак, вы хотите использовать маршрутизацию с Вашим Redux-приложением. Для этого вы можете использовать React Router. Тогда Redux будет источником правдивых данных, а React Router — единственным источником URL. В большинстве случаев, правильно разделять эти понятия, но до тех пор, пока вам не понадобится путешествовать во времени (Time Travel) и перематывать экшены, которые изменяют URL.
Установка React Router DOM
react-router-dom
доступно в npm . В этом руководстве преполагается использование версии react-router-dom@^4.1.1
.
npm install --save react-router-dom
Настройка запасного URL
Перед внедрением React Router нам требуется настроить наш сервер разработки. Конечно, наш сервер может не знать о роутах, заявленных в настройках React Router. Например, если вы переходите по ссылке /todos
, то Ваш сервер должен возвращать index.html
, так как это одностраничное приложение. Далее идут примеры настройки популярных серверов.
Важно при использовании Create React App
Если вы используете Create React App, то вам не требуется настраивать запасной URL. Это уже сделано автоматически.
Настройка Express
Если вы получаете index.html
из Express:
app.get('/*', (req,res) => {
res.sendfile(path.join(__dirname, 'index.html'))
})
Настройка WebpackDevServer
Если вы получаете index.html
из WebpackDevServer:
Добавьте в webpack.config.dev.js:
devServer: {
historyApiFallback: true,
}
Подключение React Router к Redux-приложению
В этой главе мы будем использовать наш пример Todos. Рекомендуем вам склонировать его для этой главы.
Во-первых, нам надо импортировать <Router />
и <Route />
из React Router. Вот как это делается:
import { BrowserRouter as Router, Route } from 'react-router-dom'
В React-приложении обычно <Route />
оборачивается в <Router />
, так что когда URL меняется, <Router />
находит часть своих роутов и рендерит их сформированные компоненты. <Route />
используется для декларативного сопоставления роутов иерархии компонентов вашего приложения. Вы можете объявить в path
путь, используемый в URL, и в component
— компонент, который должен быть сгенерирован при совпадении с этим URL.
const Root = () => (
<Router>
<Route path="/" component={App} />
</Router>
)
Однако, в нашем Redux-приложении нам все еще требуется <Provider />
— компонент высшего порядка, поставляемый с React Redux, который позволяет привязать Redux к React (см. Использование с React).
Далее мы импортируем <Provider />
из React Redux:
import { Provider } from 'react-redux';
Обернем <Router />
в <Provider />
, таким образом обработчики маршрутизации смогут получить доступ к стору
.
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
)
Теперь компонент <App />
будет отрендерен, если URL содержит '/'. Кроме того, мы будем добавлять необязательный :filter?
параметр к /
. Нам это потребуется позже, когда мы будем читать параметр :filter
из URL.
<Route path="/:filter?" component={App} />
Полный код компонента
components/Root.js
import React from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import { BrowserRouter as Router, Route } from 'react-router-dom'
import App from './App'
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/:filter?" component={App} />
</Router>
</Provider>
)
Root.propTypes = {
store: PropTypes.object.isRequired
}
export default Root
Нам также надо отрефакторить index.js
, для того, чтобы рендерить <Root />
компонент в DOM.
index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import todoApp from './reducers'
import Root from './components/Root'
const store = createStore(todoApp)
render(<Root store={store} />, document.getElementById('root'))
Навигация при помощи React Router
React Router поставляется с компонентом <Link />
который позволяет перемещаться по приложению. Если вы захотите добавить некоторые стили, react-router-dom
предоставляет специальный<Link />
называемый <NavLink />
, который принимает параметры стилизации. Параметр activeStyle
позволяет применить стиль активного состояния.
containers/FilterLink.js
import React from 'react'
import { NavLink } from 'react-router-dom'
const FilterLink = ({ filter, children }) => (
<NavLink
exact
to={filter === 'SHOW_ALL' ? '/' : `/${filter}`}
activeStyle={{
textDecoration: 'none',
color: 'black'
}}
>
{children}
</NavLink>
)
export default FilterLink
components/Footer.js
import React from 'react'
import FilterLink from '../containers/FilterLink'
import { VisibilityFilters } from '../actions'
const Footer = () => (
<p>
Show: <FilterLink filter={VisibilityFilters.SHOW_ALL}>All</FilterLink>
{', '}
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>Active</FilterLink>
{', '}
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>Completed</FilterLink>
</p>
)
export default Footer
Теперь, если вы кликнете на <FilterLink />
, вы увидите, что URL изменится с '/SHOW_COMPLETED'
, '/SHOW_ACTIVE'
и '/'
. Даже если вы захотите перейти назад, используя соответствующую кнопку браузера, приложение будет использовать для этого историю Вашего браузера и успешно перейдет к предыдущему URL.
Чтение из URL
Сейчас todo list не фильтруется после изменения URL. Это происходит потому, что фильтрация описана в функции mapStateToProps()
компонента <VisibleTodoList />
, которая в свою очередь связана с состоянием
, а не с URL. mapStateToProps
имеет второй необязательный аргумент ownProps
— объект, содержащий все параметры, переданные в <VisibleTodoList />
.
containers/VisibleTodoList.js
const mapStateToProps = (state, ownProps) => {
return {
todos: getVisibleTodos(state.todos, ownProps.filter)
// ранее было getVisibleTodos(state.todos, state.visibilityFilter)
};
};
В данный момент мы ничего не передаем в <App />
, поэтому ownProps
является пустым объектом. Чтобы отфильтровать наши todos в соответствии с URL, нам надо передать параметры URL в <VisibleTodoList />
.
Когда мы написали: <Route path="/:filter?" component={App} />
, это позволило в компоненте App
получить доступ к параметру params
.
Параметр params
— это объект со всеми параметрами из URL, доступный в объекте match
. Например, match.params
эквивалетно { filter: SHOW_COMPLETED' }
, если URL localhost:3000/SHOW_COMPLETED
. Теперь мы можем считывать URL из компонента <App />
.*
Важно помнить, что мы используем ES6-деструкцию на параметрах, чтобы передать их в params
в <VisibleTodoList />
.
components/App.js
const App = ({ match: { params } }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
Следующие шаги
Теперь, когда мы знаем основы создания простой навигации, мы можем перейти к изучению React Router API.
Помните, что существуют и другие библиотеки для навигации по приложению
Redux Router — экспериментальная библиотека, она позволяет Вам хранить все состояние Вашего URL в redux сторе. У нее то же самое API, что и у React Router, но сообщество и поддержка меньше, чем у React Router.
React Router Redux создает связь между Вашим Redux-приложением и React Router и позволяет их синхронизировать. Без этой связи у Вас не будет возможности перемещаться по экшенам при помощи Time Travel. Пока Вам это не требуется, React-router и Redux могут работать совершенно независимо.