## 异步`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,
...
}
~~~
如果你就这样直接运行的话,是会报错的,会提示:
![](https://box.kancloud.cn/806e16db165c6ab08299e02d54977586_635x151.png)
它告诉你`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`里定义的请求错误信息)。
![](https://box.kancloud.cn/26093638d13ec939e959140f70be5b73_431x302.png)
到这里`redux`集成基本告一段落了,后面我们还会有一些优化。