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的代码阅读就是按照这个流程来完成。

  1. 先看看我们把一个完整的操作分为几步
/*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),
  ];
}


你可能感兴趣的:(redux-sagas异步操作(2)-以ireading app为例)