## 異步`action`
參考地址: http://cn.redux.js.org/docs/advanced/AsyncActions.html
想象一下我們調用一個異步get請求去后臺請求數據:
1. 請求開始的時候,界面轉圈提示正在加載。`isLoading`置為`true`。
2. 請求成功,顯示數據。`isLoading`置為`false`,`data`填充數據 。
3. 請求失敗,顯示失敗。`isLoading`置為`false`, 顯示錯誤信息 。
下面,我們以向后臺請求用戶基本信息為例:
1、我們先創建一個`user.json`,當客戶端請求這個文件時,相當于后臺的`API`接口。
~~~
cd dist
mkdir api
cd api
touch user.json
~~~
`dist/api/user.json`
~~~
{
"name": "Zep",
"intro": "please give me a star"
}
~~~
2、創建必須的`action`創建函數。
~~~
cd src/redux/actions
touch userInfo.js
~~~
`src/redux/actions/userInfo.js`
~~~
export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST"
export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS"
export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL"
// 請求中
function getUserInfoRequest() {
return {
type: GET_USER_INFO_REQUEST
}
}
// 請求成功
function getUserInfoSuccess(userInfo) {
return {
type: GET_USER_INFO_SUCCESS,
userInfo: userInfo
}
}
// 請求失敗
function getUserInfoFail() {
return {
type: GET_USER_INFO_FAIL
}
}
~~~
我們創建了請求中,請求成功,請求失敗三個`action`創建函數。
3、創建`reducer`。
再強調一次,`reducer`是根據`state`和`action`生成新的`state`的純函數。
`src/redux/reducers/userInfo.js`
~~~
import {
GET_USER_INFO_REQUEST,
GET_USER_INFO_SUCCESS,
GET_USER_INFO_FAIL
} from 'actions/userInfo'
const initState = {
isLoading: false,
userInfo: {},
errorMsg: ''
}
export default function reducer(state = initState, action) {
switch (action.type) {
case GET_USER_INFO_REQUEST:
return {
...state,
isLoading: true,
userInfo: {},
errorMsg: ''
}
case GET_USER_INFO_SUCCESS:
return {
...state,
isLoading: false,
userInfo: action.userInfo,
errorMsg: ''
}
case GET_USER_INFO_FAIL:
return {
...state,
isLoading: false,
userInfo: {},
errorMsg: '請求錯誤'
}
default: return state
}
}
~~~
**這里的`...state`語法,是和別人的`Object.assign()`起同一個作用,合并新舊`state`。**
更新`src/redux/reducer.js`
~~~
import counter from 'reducers/counter'
import userInfo from 'reducers/userInfo'
export default funtion combineReducers(state = {}, action) {
return {
counter: counter(state.counter, action),
userInfo: userInfo(state.userInfo,action)
}
}
~~~
4、現在有了`action`,有了`reducer`,我們就需要調用把`action`里面的三個`action`函數和網絡請求結合起來。
* 請求中 `dispatch getUserInfoRequest`
* 請求成功 `dispatch getUserInfoSuccess`
* 請求失敗 `dispatch getUserInfoFail`
`src/redux/actions/userInfo.js`添加:
~~~
// ... 前面代碼省略
// 請求中
function getUserInfoRequest() {
return {
type: GET_USER_INFO_REQUEST
}
}
// 請求成功
function getUserInfoSuccess(userInfo) {
return {
type: GET_USER_INFO_SUCCESS,
userInfo: userInfo
}
}
// 請求失敗
function getUserInfoFail() {
return {
type: GET_USER_INFO_FAIL
}
}
export function getUserInfo() {
return function (dispatch) {
dispatch(getUserInfoRequest())
// 這里并沒有安裝fetch包,使用的是MDN規范API,路徑根據項目可能稍有不同
return fetch('http://10.10.100.217:10088/api/userInfo.json')
.then((res) => {
return res.json()
})
.then((json) => {
dispatch(getUserInfoSuccess(json)
}).catch(() => {
dispatch(getUserInfoFail())
})
}
}
~~~
有沒有發現這里和我們之前寫的`action`創建函數**不一樣**,別的`action`創建函數返回的都是普通的`action`對象:
~~~
{
type: xxxx,
...
}
~~~
如果你就這樣直接運行的話,是會報錯的,會提示:

它告訴你`action`創建函數返回的必須是普通的`action`對象,不過它也提示了我們應該使用中間件處理這類`actions`。
所以我們為了讓`action`創建函數除了返回`action`對象外,還可以返回函數,我們需要引用`redux-thunk`。
`npm install --save redux-thunk`
這里涉及到`redux`中間件`middleware`,后面會講到的。你也可以讀這里[Middleware](http://cn.redux.js.org/docs/advanced/Middleware.html)。
簡單地說,中間件就是`action`在到達`reducer`(處理`state`發生變化的地方)之前,先經過中間件處理,我們之前知道`reducer`能處理的`action`只能是普通對象包含一個必須字段`type`的普通對象,所以我們使用中間件來處理函數形式的`action`,把它們轉為標準的`action`給`reducer`,這就是`redux-thunk`的作用。
下面來使用`redux-thunk`中間件:
~~~
/* store.js */
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import combineReducers from './reducers'
export default createStore(combineReducers, applyMiddleware(thunkMiddleware))
~~~
這樣就OK了~加一個路由頁面試一下。
`src/pages/UserInfo/UserInfo.js`
~~~
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { getUserInfo } from 'actions/userInfo'
class UserInfo extends Component {
render() {
const { userInfo, isLoading, errorMsg } = this.props.userInfo
// 根據 redux state isLoading 切換狀態
return (
<div>
{
isLoading ? '請求中...' :
(errorMsg ? errorMsg :
<div>
<p>用戶信息:</p>
<p>用戶名:{ userInfo.name }</p>
<p>介紹: { userInfo.intro }</p>
</div>
)
}
<button onClick={ () => this.props.getUserInfo() }>請求用戶信息</button>
</div>
)
}
}
export default connect( (state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo)
~~~
這里你可能發現`connect`參數的寫法不一樣了,`mapStateToProps`函數用了`es6`的簡寫`()`里的東西會默認被`return`出去,`mapDispatchToProps`用了`react-redux`提供的簡單寫法。
增加路由`sc/router/router.js`:
~~~
import React from 'react'
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'
import Home from 'pages/Home/Home'
import Page from 'pages/Page/Page'
import Counter from 'pages/Counter/Counter'
export default () => (
<Router>
<div>
<ul>
<li><Link to="/">首頁</Link></li>
<li><Link to="/page">Page</Link></li>
<li><Link to="/counter">Counter</Link></li>
<li><Link to="/userinfo">UserInfo</Link></li>
</ul>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page" component={Page} />
<Route path="/counter" component={Counter}/>
<Route path="/userinfo" component={UserInfo}/>
</Switch>
</div>
</Router>
// 相當于return 一個(組件)
)
~~~
現在可以執行`npm run dev`看效果了~(如果請求路徑錯了或者跨域問題請求失敗了,都會提示`reducer`里定義的請求錯誤信息)。

到這里`redux`集成基本告一段落了,后面我們還會有一些優化。