美文网首页javascriptreact
redux-sagas异步操作(2)-以ireading app

redux-sagas异步操作(2)-以ireading app

作者: smartphp | 来源:发表于2016-10-30 03:00 被阅读1518次

    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

        本文链接:https://www.haomeiwen.com/subject/szwyuttx.html