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),
  ];
}


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容

  • 看到这篇文章build an image gallery using redux saga,觉得写的不错,长短也适...
    smartphp阅读 6,150评论 1 29
  • 一、什么情况需要redux? 1、用户的使用方式复杂 2、不同身份的用户有不同的使用方式(比如普通用户和管...
    初晨的笔记阅读 2,025评论 0 11
  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,902评论 23 111
  • 应用程序使用很多对象在内存中保存对象会影响内存性能对象的多数特有状态可以放到外部而轻量化移除了外在状态之后,可以用...
    Crazy2015阅读 311评论 0 0
  • 雨点 打落在窗沿 渐渐步入黄昏的时针 预示着即将来临的霓虹灯 在天桥下等 一颗一颗闪亮的明星 一个一个过往的人 可...
    兰亭没有序阅读 259评论 1 2