ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 异步`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`集成基本告一段落了,后面我们还会有一些优化。