Использование с React Router
Вы хотите реализовать маршрутизацию в своем приложении Redux. Вы можете использовать React Router. Redux будет источником истинности для ваших данных, а React Router — для ваших URL. В большинстве случаев их можно разделять, если только вам не нужно путешествовать во времени и отменять действия, которые вызывают изменение URL.
Установка React Router
react-router доступен на npm. Данное руководство предполагает, что вы используете react-router@^2.7.0.
npm install --save react-router
Настройка 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 { Router, Route } from 'react-router'
В приложении React обычно вы обернули бы <Route /> в <Router />, чтобы при изменении URL <Router /> подбирал ветвь своих маршрутов и рендерил соответствующие компоненты. <Route /> используется для декларативного сопоставления маршрутов с иерархией компонентов вашего приложения. Вы бы объявили в path путь, используемый в URL, и в component отдельный компонент, который будет рендериться, когда маршрут соответствует URL.
const Root = () => (
<Router>
<Route path="/" component={App} />
</Router>
)
Однако в нашем приложении Redux нам все еще понадобится <Provider />. <Provider /> — это высший компонент, предоставляемый React Redux, который позволяет привязать Redux к React (см. Использование с React).
Затем мы импортируем <Provider /> из React Redux:
import { Provider } from 'react-redux'
Мы обернем <Router /> в <Provider />, чтобы обработчики маршрутов могли получить доступ к store.
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
)
Теперь компонент <App /> будет рендериться, если URL соответствует '/'. Кроме того, мы добавим необязательный параметр (:filter) к /, поскольку нам понадобится он позже, когда мы будем пытаться прочитать параметр (:filter) из URL.
<Route path="/(:filter)" component={App} />
Вы, вероятно, захотите удалить хэш из URL (например: http://localhost:3000/#/?_k=4sbb0i). Для этого вам нужно будет также импортировать browserHistory из React Router:
import { Router, Route, browserHistory } from 'react-router'
и передать его в <Router /> для удаления хэша из URL:
<Router history={browserHistory}>
<Route path="/(:filter)" component={App} />
</Router>
Если вы не ориентируетесь на старые браузеры, такие как IE9, вы всегда можете использовать browserHistory.
components/Root.js
import React from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import App from './App'
const Root = ({ store }) => (
<Provider store={store}>
<Router history={browserHistory}>
<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'
let store = createStore(todoApp)
render(
<Root store={store} />,
document.getElementById('root')
)
Навигация с React Router
React Router поставляется с компонентом <Link />, который позволяет перемещаться по вашему приложению. В нашем примере мы можем обернуть <Link /> в новый контейнерный компонент <FilterLink />, чтобы динамически изменять URL. Свойство activeStyle={} позволяет нам применять стиль к активному состоянию.
containers/FilterLink.js
import React from 'react'
import { Link } from 'react-router'
const FilterLink = ({ filter, children }) => (
<Link
to={filter === 'SHOW_ALL' ? '/' : filter}
activeStyle={{
textDecoration: 'none',
color: 'black'
}}
>
{children}
</Link>
)
export default FilterLink
components/Footer.js
import React from 'react'
import FilterLink from '../containers/FilterLink'
const Footer = () => (
<p>
Show:
{' '}
<FilterLink filter="SHOW_ALL">
All
</FilterLink>
{', '}
<FilterLink filter="SHOW_ACTIVE">
Active
</FilterLink>
{', '}
<FilterLink filter="SHOW_COMPLETED">
Completed
</FilterLink>
</p>
)
export default Footer
Теперь, если вы щелкните по <FilterLink />, вы увидите, что ваш URL будет меняться между '/SHOW_COMPLETED', '/SHOW_ACTIVE', и '/'. Даже если вы возвращаетесь назад с помощью браузера, он будет использовать историю браузера и эффективно переходить к вашему предыдущему URL.
Чтение из URL
В настоящее время список задач не фильтруется даже после изменения URL. Это потому, что мы фильтруем из <VisibleTodoList />, а mapStateToProps() по-прежнему привязано к state, а не к URL. mapStateToProps имеет необязательный второй аргумент ownProps, который представляет собой объект со всеми свойствами, переданными в <VisibleTodoList />
containers/VisibleTodoList.js
const mapStateToProps = (state, ownProps) => {
return {
todos: getVisibleTodos(state.todos, ownProps.filter) // previously was getVisibleTodos(state.todos, state.visibilityFilter)
}
}
Сейчас мы ничего не передаем в <App />, поэтому ownProps — пустой объект. Чтобы отфильтровать наши задачи в соответствии с URL, мы хотим передать параметры URL в <VisibleTodoList />.
Когда ранее мы писали: <Route path="/(:filter)" component={App} />, это делало доступным внутри App свойство params.
Свойство params — это объект со всеми параметрами, указанными в URL. Например: params будет равно { filter: 'SHOW_COMPLETED' }, если мы перейдем по ссылке localhost:3000/SHOW_COMPLETED. Теперь мы можем прочитать URL из <App />.
Обратите внимание, что мы используем ES6 деструктуризацию свойств для передачи в params в <VisibleTodoList />.
components/App.js
const App = ({ params }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
Следующие шаги
Теперь, когда вы знаете, как выполнять базовая маршрутизацию, вы можете узнать больше о API React Router
Примечание об других библиотеках маршрутизации
Redux Router — это экспериментальная библиотека, которая позволяет хранить состояние вашего URL полностью в хранилище Redux. Она имеет тот же API, что и React Router API, но имеет меньшую поддержку сообщества, чем react-router.
React Router Redux создает связь между вашим приложением Redux и react-router и синхронизирует их. Без этой связи вы не сможете отменить действия при использовании путешествия во времени. Если вам это не нужно, React Router и Redux могут работать совершенно независимо.
© 2015–2017 Dan Abramov
Licensed under the MIT License.
http://redux.js.org/docs/advanced/UsageWithReactRouter.html