# 搭配 React Router
现在你想在你的 Redux 应用中使用路由功能,可以搭配使用 [React Router](https://github.com/reactjs/react-router) 来实现。
Redux 和 React Router 将分别成为你数据和 URL 的事实来源(the source of truth)。
在大多数情况下, **最好** 将他们分开,除非你需要时光旅行和回放 action 来触发 URL 改变。
## 安装 React Router
可以使用 npm 来安装 `react-router-dom`。本教程基于 `react-router-dom@^4.1.1` 。
`npm install --save react-router-dom`
## 配置后备(fallback) URL
在集成 React Router 之前,我们需要配置一下我们的开发服务器。
显然,我们的开发服务器无法感知配置在 React Router 中的 route。
比如:你想访问并刷新 `/todos`,由于是一个单页面应用,你的开发服务器需要生成并返回 `index.html`。
这里,我们将演示如何在流行的开发服务器上启用这项功能。
> ### 使用 Create React App 须知
>
> 如果你是使用 Create React App (你可以点击[这里](https://facebook.github.io/react/blog/2016/07/22/create-apps-with-no-configuration.html)了解更多,译者注)工具来生成项目,会自动为你配置好后备(fallback) URL。
### 配置 Express
如果你使用的是 Express 来返回你的 `index.html` 页面,可以增加以下代码到你的项目中:
```js
app.get('/*', (req, res) => {
res.sendfile(path.join(__dirname, 'index.html'))
})
```
### 配置 WebpackDevServer
如果你正在使用 WebpackDevServer 来返回你的 `index.html` 页面,
你可以增加如下配置到 webpack.config.dev.js:
```js
devServer: {
historyApiFallback: true,
}
```
## 连接 React Router 和 Redux 应用
在这一章,我们将使用 [Todos](https://github.com/reactjs/redux/tree/master/examples/todos) 作为例子。我们建议你在阅读本章的时候,先将仓库克隆下来。
首先,我们需要从 React Router 中导入 `<Router />` 和 `<Route />`。代码如下:
```js
import { BrowserRouter as Router, Route } from 'react-router-dom'
```
在 React 应用中,通常你会用 `<Router />` 包裹 `<Route />`。
如此,当 URL 变化的时候,`<Router />` 将会匹配到指定的路由,然后渲染路由绑定的组件。
`<Route />` 用来显式地把路由映射到应用的组件结构上。
用 `path` 指定 URL,用 `component` 指定路由命中 URL 后需要渲染的那个组件。
```js
const Root = () => (
<Router>
<Route path="/" component={App} />
</Router>
)
```
另外,在我们的 Redux 应用中,我们仍将使用 `<Provider />`。
`<Provider />` 是由 React Redux 提供的高阶组件,用来让你将 Redux 绑定到 React (详见 [搭配 React](../basics/UsageWithReact.md))。
然后,我们从 React Redux 导入 `<Provider />`:
```js
import { Provider } from 'react-redux'
```
我们将用 `<Provider />` 包裹 `<Router />`,以便于路由处理器可以[访问 `store`](../basics/UsageWithReact.html#passing-the-store)。
```js
const Root = ({ store }) => (
<Provider store={store}>
<Router>
<Route path="/" component={App} />
</Router>
</Provider>
)
```
现在,如果 URL 匹配到 '/',将会渲染 `<App />` 组件。此外,我们将在 '/' 后面增加参数 `:filter?`,
以便以后我们从 URL 中读取参数 `:filter`。
```js
<Route path="/:filter?" component={App} />
```
也许你想将 '#' 从 URL 中移除(例如:`http://localhost:3000/#/?_k=4sbb0i`)。
你需要从 React Router 导入 `browserHistory` 来实现:
```js
import { Router, Route, browserHistory } from 'react-router'
```
#### `components/Root.js`
```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`
```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 />`](https://github.com/reactjs/react-router/blob/master/docs/API.md#link) 来实现导航功能。 如果你需要自定义样式,`react-router-dom` 提供了另一个可以自定义样式的特殊 `<Link />` 叫做 [`<NavLink />`](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/NavLink.md)。 例如你可以通过 `activeStyle` 属性来指定激活状态的样式。
在我们的示例中,我们可以在一个新的容器组件 `<FilterLink/>` 中 使用 `<NavLink/>` ,以便动态地更改 URL。
### `containers/FilterLink.js`
```js
import React from 'react'
import { NavLink } from 'react-router-dom'
const FilterLink = ({ filter, children }) => (
<NavLink
to={filter === 'SHOW_ALL' ? '/' : `/${filter}`}
activeStyle={{
textDecoration: 'none',
color: 'black'
}}
>
{children}
</NavLink>
)
export default FilterLink
```
### `components/Footer.js`
```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 中读取数据
现在,即使 URL 改变,todo 列表也不会被过滤。
这是因为我们是在 `<VisibleTodoList />` 的 `mapStateToProps()` 函数中过滤的。
这个目前仍然是和 `state` 绑定,而不是和 URL 绑定。
`mapStateToProps` 的第二可选参数 `ownProps`,这个是一个传递给 `<VisibleTodoList />` 所有属性的对象。
### `containers/VisibleTodoList.js`
```js
const mapStateToProps = (state, ownProps) => {
return {
todos: getVisibleTodos(state.todos, ownProps.filter) // 以前是 getVisibleTodos(state.todos, state.visibilityFilter)
}
}
```
目前我们还没有传递任何参数给 `<App />`,所以 `ownProps` 依然是一个空对象。
为了能够根据 URL 来过滤我们的 todo 列表,我们需要向 `<VisibleTodoList />` 传递 URL 参数。
之前我们写过:`<Route path="/:filter?" component={App} />`,这使得可以在 `App` 中获取 `params` 的属性。
`params` 是一个包含 url 中所有指定参数的对象。
_例如:如果我们访问 `localhost:3000/SHOW_COMPLETED`,那么 `match.params` 将等价于 `{ filter: 'SHOW_COMPLETED' }`。
现在,我们可以在 `<App />` 中读取 URL 参数了。_
注意,我们将使用 [ES6 的解构赋值](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)来对 `params` 进行赋值,以此传递给 `<VisibleTodoList />`。
### `components/App.js`
```js
const App = ({ match: { params } }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
```
## 下一步
现在你已经知道如何实现基础的路由,接下来你可以阅读 [React Router API](http://reacttraining.cn/) 来学习更多知识。
> ##### 其它路由库注意点
> _Redux Router_ 是一个实验性质的库,它使得你的 URL 的状态和 redux store 内部状态保持一致。它有和 React Router 一样的 API,但是它的社区支持比 react-router 小。
> _React Router Redux_ 将你的 redux 应用和 react-router 绑定在一起,并且使它们保持同步。如果没有这层绑定,你将不能通过时光旅行来回放 action。除非你需要这个,不然 React-router 和 Redux 完全可以分开操作。
- 自述
- 介绍
- 动机
- 核心概念
- 三大原则
- 先前技术
- 学习资源
- 生态系统
- 示例
- 基础
- Action
- Reducer
- Store
- 数据流
- 搭配 React
- 示例:Todo List
- 高级
- 异步 Action
- 异步数据流
- Middleware
- 搭配 React Router
- 示例:Reddit API
- 下一步
- 技巧
- 配置 Store
- 迁移到 Redux
- 使用对象展开运算符
- 减少样板代码
- 服务端渲染
- 编写测试
- 计算衍生数据
- 实现撤销重做
- 子应用隔离
- 组织 Reducer
- Reducer 基础概念
- Reducer 基础结构
- Reducer 逻辑拆分
- Reducer 重构示例
- combineReducers 用法
- combineReducers 进阶
- State 范式化
- 管理范式化数据
- Reducer 逻辑复用
- 不可变更新模式
- 初始化 State
- 结合 Immutable.JS 使用 Redux
- 常见问题
- 综合
- Reducer
- 组织 State
- 创建 Store
- Action
- 不可变数据
- 代码结构
- 性能
- 设计哲学
- React Redux
- 其它
- 排错
- 词汇表
- API 文档
- createStore
- Store
- combineReducers
- applyMiddleware
- bindActionCreators
- compose
- react-redux 文档
- API
- 排错