# 搭配 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
- 排錯