redux异步数据流(一)#
前言
redux作为状态管理的解决方案,运行时大体流程如下:用户触发某个操作,对应的操作会触发(dispatch)action(由action creator执行返回),reducer根据action类型返回新的state,view重新渲染。需要注意的是redux原始的dispacth方法接受的action必须是一个对象,redux的dispatch方法源码如下:
假设有一个这样的场景,一个组件点击按钮查询展示来自服务端的列表信息,流程如下:
- 点击按钮请求开始,页面显示“正在查询。。。
- 如果请求成功,页面显示查询到的数据列表。
- 如果请求失败,页面显示“出现错误,请重试。。。
页面显示的依据均是由redux状态判断,故由此可以得出以下三个action。
- 通知 reducer 请求开始的 action。
- 通知 reducer 请求成功结束的 action。
- 通知 reducer 请求失败的 action。
action creator部分及reducer代码如下:
// constants
const FETCH_CNODE_LIST = 'FETCH_CNODE_LIST';
const FETCH_CNODE_FAILURE = 'FETCH_CNODE_FAILURE';
const FETCH_CNODE_SUCCESS = 'FETCH_CNODE_SUCCESS';
// reducer
const listReducer = (state = { isFetching: false, hasError: false, data: [] }, action) => {
switch (action.type) {
case FETCH_CNODE_LIST:
return Object.assign({}, state, { isFetching: true, hasError: false });
case FETCH_CNODE_FAILURE:
return Object.assign({}, state, { hasError: true, isFetching: false });
case FETCH_CNODE_SUCCESS:
return Object.assign({}, state, { data: action.data, isFetching: false, hasError: false });
default:
return state
}
}
// action creator
const beginFectch = () => {
return {
type: FETCH_CNODE_LIST
}
}
const fectchFail = () => {
return {
type: FETCH_CNODE_FAILURE
}
}
const fectchSuccess = (data) => {
return {
type: FETCH_CNODE_SUCCESS,
data: data
}
}
由上可以看出action creator所返回的都是对象。
// CnodeList component
class CnodeList extends React.Component {
constructor(props) {
super(props);
this.getNodeList = this.getNodeList.bind(this);
}
render() {
const { cnode } = this.props;
var str = '';
if (cnode.isFetching) {
str = '正在查询。。。。';
}
else if (cnode.hasError) {
str = '出现错误,请重试。。。';
} else {
if (cnode.data && cnode.data.length > 0) {
str = cnode.data.map(obj =>
<li key={obj.id}>
<strong>id:</strong><span style=marginRight>{obj.id}</span>
<strong>author_id:</strong><span>{obj.author_id}</span>
</li>
);
} else {
str = <li>请查询。。。</li>;
}
}
return (
<div>
<button onClick={this.getNodeList.bind(this, 'ask')}>查询</button>
<ul>{str}</ul>
<button onClick={this.getNodeList.bind(this, 'ask')}>1</button>
<button onClick={this.getNodeList.bind(this, 'share')}>2</button>
<button onClick={this.getNodeList.bind(this, 'job')}>3</button>
<button onClick={this.getNodeList.bind(this, 'good')}>4</button>
</div>
)
}
getNodeList(tab) {
const { getNodeList } = this.props;
getNodeList(tab);
}
}
// CnodeListWrapper实现的代码在下面
class App extends React.Component {
render() {
return (
<div>
<CnodeListWrapper></CnodeListWrapper>
</div>
)
}
}
ReactDOM.render(
<App></App>,
document.getElementById('root')
)
如果单纯的使用redux方式来进行异步处理,CnodeListWrapper实现代码如下:
class CnodeListWrapper extends React.Component {
constructor(props) {
super(props);
this.state = store.getState();
this.getNodeList = this.getNodeList.bind(this);
this.unSubscribeHandle = null;
}
getNodeList(tab) {
store.dispatch(beginFectch());
// 假设tab为"job"时触发错误
if (tab === 'job') {
store.dispatch(fectchFail());
return;
}
$.ajax({
url: `https://cnodejs.org/api/v1/topics?tab=${tab}&limit=20`,
success: function (result) {
if (result.success && result.data) {
store.dispatch(fectchSuccess(result.data));
} else {
store.dispatch(fectchFail());
}
},
error: function (jqXHR, textStatus, errorThrown) {
store.dispatch(fectchFail());
}
});
}
componentDidMount() {
this.unSubscribeHandle = store.subscribe(() => {
this.setState(store.getState())
});
}
componentWillUnmount() {
this.unSubscribeHandle();
}
render() {
return (
<CnodeList getNodeList={this.getNodeList} cnode={this.state.cnode}></CnodeList>
)
}
}
如上CnodeListWrapper的实现方式增加了数据与组件之间的耦合度不利于后期维护,假如又存在一个组件所依赖的数据源是与CnodeListWrapper相同,上述异步获取数据的代码只能重复的书写一份。
基于上述问题可以我们采用一个中间件重构redux的dispatch方法,重构的方式有很多目前流行的有redux-thunk、redux-promise、redux-saga等。
redux-thunk
上述异步获取数据的代码可以提取成为一个函数,由action creator返回一个函数,该函数作为参数传入store.dispatch,需要注意的是redux的dispatch参数只接受对象,而不是函数,redux-thunk目的就是对store.dispatch进行重构,使其支持函数作为参数。源码如下:
由上述代码可以看出redux-thunk无非是做一个是否为函数的判断,如果是函数将dispatch、getState等作为参数传入到函数内部,举例代码可以修改如下(有些带忽略):
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import $ from 'jquery'
// ....
let createStoreWithMiddleware = applyMiddleware(thunk, logger)(createStore);
let store = createStoreWithMiddleware(reducer);
// ....
// action creator
const fetchNodeList = ({ tab }) => {
return (dispatch, getState) => {
dispatch(beginFectch());
$.ajax({
url: `https://cnodejs.org/api/v1/topics?tab=${tab}&limit=20`,
success: function (result) {
if (result.success && result.data) {
dispatch(fectchSuccess(result.data));
} else {
dispatch(fectchFail());
}
},
error: function (jqXHR, textStatus, errorThrown) {
dispatch(fectchFail());
}
});
}
}
// ....
// CnodeListWrapper
class CnodeListWrapper extends React.Component {
constructor(props) {
super(props);
this.state = store.getState();
this.getNodeList = this.getNodeList.bind(this);
this.unSubscribeHandle = null;
}
getNodeList(tab) {
store.dispatch(fetchNodeList({tab}));
}
componentDidMount() {
this.unSubscribeHandle = store.subscribe(() => {
this.setState(store.getState())
});
}
componentWillUnmount() {
this.unSubscribeHandle();
}
render() {
return (
<CnodeList getNodeList={this.getNodeList} cnode={this.state.cnode}></CnodeList>
)
}
}
这样获取列表数据的任务就交由action creator负责,这样的修改减少了组件的耦合性。往往我们都会将react-redux集成进来,方便获取redux的state代码如下:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { connect, Provider } from 'react-redux'
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import $ from 'jquery'
// ....
const mapStateToProps = (state) => {
return {
cnode: state.cnode
}
}
const mapDispatchToProps = dispatch => {
return {
getNodeList: (tab) => {
dispatch(fetchNodeList({ tab }))
}
}
}
const CnodeListWrapper = connect(mapStateToProps, mapDispatchToProps)(CnodeList);
// ....
ReactDOM.render(
<Provider store={store}>
<App></App>
</Provider>,
document.getElementById('root')
)
通过使用redux-thunk中间件修改store.dispatch方法,使其能接受函数作为参数,但使用redux-thunk最大的问题,就是重复的模板代码(action creator、reducer的重复戴代码)太多。那么也可以通过其他中间件将其修改为接受其他类型的参数。下面讲述redux-promise
redux-promise
redux-promise这个中间件就是将store.dispatch方法可以接受promise对象,redux-promise源码如下:
从上述代码中可以发现 “isFSA”方法是用来判断它是否是标准的flux action格式。 isFSA实现的源码如下:
可以用两种方式使用redux-promise:1.action直接为promise。 2.action对象的payload属性为promise。使用方式如下:
方式一
// constants
const FETCH_CNODE_LIST = 'FETCH_CNODE_LIST';
// 方式1 action creator
const fectcList = (data) => {
return {
type: FETCH_CNODE_LIST,
payload: data
}
}
const fetchNodeList = ({ tab }) => new Promise(function (resolve, reject) {
return $.get(`https://cnodejs.org/api/v1/topics?tab=${tab}&limit=20`).then(result => {
resolve(fectcList(result.data));
});
});
有一点需要注意的是redux-promise会判断当前的action是否满足FSA格式,如果不满足,走方式一的路线(action直接为promise对象),但可以发现方式一只能执行resolve后结果,无法执行reject的结果。
方式二
// constants
const FETCH_CNODE_LIST = 'FETCH_CNODE_LIST';
// action creator
const fetchNodeList = createAction(FETCH_CNODE_LIST, ({tab}) => {
return new Promise(function (resolve, reject) {
$.get(`https://cnodejs.org/api/v1/topics?tab=${tab}&limit=20`).then(function (result) {
resolve(result.data);
}, function () {
reject({ error: true })
});
}) });
未完待续。。。