redux-sagas异步操作(2)-以ireading app
ireading是一个比较好的微信精选的app,里面用到了redux-sagas组件。我们就来看看怎么使用这个组件来异步从api接口获取数据。在这个app中还有几个比较重的内容需要学习,一个listview的下拉刷新的解决办法,或者叫代码段,一个是tabview的item的动态建立的思路,tebview的项目不是写死的而是通过数据api动态来获取,灵活性大了很多。
这个app我也看了好多遍,但是由于是新手所以有些地方可能理解的有问题,请谅解。
如果比我还菜鸟的,请先理解Redux的内容,参考redux中文文档,理解redux-counter就可以,这个里面只有一个状态,学习起来比较容易。对应的 react-native也有一个redux-counter的app,我自己也用NativeBase组件库进行了简单的包装,过程是非常的简单,由于只涉及到UI部分的更改,在redux的帮助下,修改UI非常容易,其他的逻辑部分都不动就可以完成。完成这个修改让我感觉到Redux的模块化设计的威力和便利性。
好了,下面对内容就假定你已经对redux的模块和数据流有个认识。redux-sagas在redux基础上把action更加深入的模块化,action现在只处理和dispatch的ationtype的匹配工作
,具体的操作都在sagas里面来完成。所以sagas并不神秘,就我现在的理解,sagas也就是使用es7的一些特性把js的异步操作做了进一步的封装。
redux-sagas中文文档
js由于是单线程的模式,相当是一个饭馆只请了一个小二,这小二要负责所有用户的点餐,送餐和收钱的工作。所以不能在一个用户定了餐以后他就什么事也不干,等着饭好以后送给第一个客户,这样的模式下实际上主要的等待时间是等饭的准备,非常耗时间,在第一个客户订餐以后,小二记录下用户座位号码和点餐内容以后就可以去服务其他的顾客。当第一个客户的餐准备好了以后,恰好这时小二手头又没有其他工作的时候,他就会把第一个顾客的餐根据记录的座位号码送到顾客手中。当然第一个顾客先定,但是他可能不是第一个拿到餐,如果他定了一个比较耗时间的餐,比如红烧排骨,这个做起来比较复杂,第二个顾客定里一个凉拌黄瓜,这个简单,所以第二个顾客可能会先拿到凉拌黄瓜。
这里面就是异步操作的所有内容,要点是一个原先完整的动作,现在把它分布细化操作步骤来完成
,比如这里的顾客用餐同步视角下我们认为是一个动作,异步视角下我们把它拆为 点餐--做餐-送餐三个步骤。那么谁来把这三个动作串联起来?在实际的餐馆中是店小二(服务员),在js中是谁呢?我认为就是Promise对象。Promise对象负责记录每个异步操作的步骤,分配编号,在将来一旦有步骤完成,事件队列中又没有正在做的工作是,他就会完成某个动作的下一步,直至所有的工作都完成。从api获取数据的app里面基本都划分为requestData,fetchData,recevieData三个步骤。
显然这种模式也有问题,假设厨房同时做好了多个菜,这个时候就够小二忙的了。处理这种情况最好的办法是多请几个小二,但是这样一来成本就大了。所以还是请一个小二性价比较好。
以上我本人对于异步操作的通俗的理解。我觉得基本可以解释时间循环队列和js异步操作的概念。redux文档里面讲了thunk这个中间件也是用来解决异步操作问题的,但是感觉不如sagas好理解。看完redux文档,直接看readux-sagas文档吧。
在啰嗦一下redux的数据流
UI component dispatch(action)->actions(requestData)->redux-sagas->actions(receiveData)->reducer(change state
)->UI component.
终点有回到起点,经过sagas把三步操作连接到一起,最终通过state的改变让UI component获得想要的内容。
好吧,ireading的代码阅读就是按照这个流程来完成。
- 先看看我们把一个完整的操作分为几步
/*ireading/app/constants.js 读取api数据的都可以这么写*/
export const REQUEST_ARTICLE_LIST = 'REQUEST_ARTICLE_LIST';//请求
export const FETCH_ARTICLE_LIST = 'FETCH_ARTICLE_LIST'; //执行
export const RECEIVE_ARTICLE_LIST = 'RECEIVE_ARTICLE_LIST';//获取数据
2 在component中直接dispatch这个三个常量,这样一旦component中dispatch一个action就准确无误的和acions中的action所匹配,就可以执行其他工作步骤了。
下面就看看component就得代码,部分代码.具体的看ireading源码
/*ireading/app/pages/Main.js*/
import * as readAction from '../actions/read'; //es6写法获取所有的action
let canLoadMore;
let page = 1;
let loadMoreTime = 0;//上面三个变量是定义加载和分页的变量
constructor(props) {
super(props);
this.state = { //listview的数据更新的定义
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
typeIds: [], //分类
typeList: {} //分类的对象
};
this.renderItem = this.renderItem.bind(this);//这几个都是为函数硬绑定当前对象
this.renderFooter = this.renderFooter.bind(this);
this.renderNavigationView = this.renderNavigationView.bind(this);
this.onIconClicked = this.onIconClicked.bind(this);
canLoadMore = false;
}
//这个是react的生命周期函数,用于在加载组件是的操作
componentDidMount() {
const { dispatch } = this.props;
//addlistener�监听Category.js中的广播,有变化就跟新typeId
//这里typeId就是分类信息,比如体育,军事,文学等等
DeviceEventEmitter.addListener('changeCategory', (typeIds) => {
typeIds.forEach((typeId) => { //遍历typeIDs,进行dispatch操作
//基本上这个app最重要的的地方就在这里了。
dispatch(readAction.requestArticleList(false, true, typeId));
});
this.setState({
typeIds
});
});
InteractionManager.runAfterInteractions(() => {
Storage.get('typeIds')
.then((typeIds) => {
if (!typeIds) {
return;
}
typeIds.forEach((typeId) => {
//与上面的相同
dispatch(readAction.requestArticleList(false, true, typeId));
});
Storage.get('typeList').then((typeList) =>
this.setState({
typeIds,
typeList
})
);
});
});
}
//点击刷新按钮的时候执行的是一样的dispatch,只是状态有改变
onRefresh(typeId) {
const { dispatch } = this.props;
canLoadMore = false;
dispatch(readAction.requestArticleList(true, false, typeId));
}
//底部下拉刷新的函数,要进行节流。
onEndReached(typeId) {
const time = Date.parse(new Date()) / 1000;
if (canLoadMore && time - loadMoreTime > 1) {
page += 1; //如果还有数据,就进行分页操作,就是页数进位
const { dispatch } = this.props;
dispatch(readAction.requestArticleList(false, false, typeId, true, page));
canLoadMore = false;
loadMoreTime = Date.parse(new Date()) / 1000;
}
}
//有关数据获取的操作就是这些内容,其他的draw和tabviw有关的,可以看源码。
2.component dispatch的动作会经过store的中转,在acitons中进行匹配
component中 dispatch(readAction.requestArticleList(false, false, typeId, true, page));
会在actions中找到对应的requestArticleList
,接着的工作就完全就redux来接管了。
/*ireading/app/actions/read.js*/
import * as types from '../constants/ActionTypes';
//这里就是获取微信文章内容动作拆分的三个动作
export function requestArticleList(isRefreshing, loading, typeId, isLoadMore, page = 1) {
return {
type: types.REQUEST_ARTICLE_LIST,
isRefreshing,
loading,
isLoadMore,
typeId,
page,
};
}
export function fetchArticleList(isRefreshing, loading, isLoadMore = false) {
return {
type: types.FETCH_ARTICLE_LIST,
isRefreshing,
loading,
isLoadMore
};
}
export function receiveArticleList(articleList, typeId) {
return {
type: types.RECEIVE_ARTICLE_LIST,
articleList,
typeId
};
}
//注意,使用了redux-sagas以后要,具体的数据操作工作都不在这里完成了,都会在sagas中进行具体的操做
//移步到sagas文件夹
3.sagas中进行具体的操作
/*ireading/app/sagas/reading.js*/
import { put, take, call, fork } from 'redux-saga/effects';//四个执行sagas的api 函数
import * as types from '../constants/ActionTypes';
import { WEXIN_ARTICLE_LIST } from '../constants/Urls';
import { fetchArticleList, receiveArticleList } from '../actions/read';
//异步请求的开始
//有关es6异步操作的内容请看`阮一峰《es6标准入门》`
//下面每一个yield执行的都是异步操作中的一个步骤,
//但是可以按照同步的方法写出来,只是执行
//的时候是异步模式,还记得店小二的例子吗?
export function* requestArticleList(isRefreshing, loading, typeId, isLoadMore, page) {
try {
yield put(fetchArticleList(isRefreshing, loading, isLoadMore));
//call函数执行的就是获取文章的具体操作,两个参数一个是call的函数,一个是参数
//request是作者自己定义的一个方法,封装了fetch模块
const articleList = yield call(request,
`${WEXIN_ARTICLE_LIST}?typeId=${typeId}&page=${page}`,
'get');
//获取内容就可有执行receiveArticleList的工作,获取内容了,如果要让compoent感知state的变化,就把获取的内容添加到state中。
yield put(receiveArticleList(articleList.showapi_res_body.pagebean.contentlist, typeId));
const errorMessage = articleList.showapi_res_error;
if (errorMessage && errorMessage !== '') {
yield toastShort(errorMessage);
}
} catch (error) {
yield put(receiveArticleList([], typeId));
toastShort('网络发生错误,请重试');
}
}
//watchRequestArticleList函数会件事dispatch(RequestArticleList)
//一旦感知到这个动作,就会开始执行requestArticleList
//正式进入请求文章的异步操作流程
export function* watchRequestArticleList() {
while (true) {
const {
isRefreshing,
loading,
typeId,
isLoadMore,
page
} = yield take(types.REQUEST_ARTICLE_LIST);
yield fork(requestArticleList, isRefreshing, loading, typeId, isLoadMore, page);
}
}
/*ireading/app/sagas/index.js*/
这个文件里又把所有的异步函数进行了打包,
在store里面需要把这些函数注入。
import { fork } from 'redux-saga/effects';
import { watchRequestTypeList } from './category';
import { watchRequestArticleList } from './read';
export default function* rootSaga() {
yield [
fork(watchRequestTypeList),
fork(watchRequestArticleList),
];
}