#博客大赛# Redux 之异步

关于Redux,相信前端开发同学一定都不陌生,虽然之前有用过 Redux , 但是对它的理解仍然停留在view -> action -> reducer -> store 的层面,对于在这个过程中如何处理异步的流程,并没有去研究过。
Redux作为React 的状态管理容器,是单向数据流的方式,只能通过 dispatch(action) 的方式改变store 中的state数据,并且它只能处理同步过程。

如果项目中使用了Redux, 面对大量的异步业务场景,以及复杂的业务逻辑,知道如何处理异步流程是非常有必要的。
有很多redux中间件用于处理异步过程,如redux-thunk、redux-promise、redux-saga、redux-observable。

在这里我主要学习下redux-observable,如果有理解的不对的地方,请帮我指出来,谢谢大家!

Redux 中间件实现异步操作

Redux 的中间件提供的是位于 action 被触发之后,到达 reducer 之前的扩展点,并且在这个扩展点位置会产生一个新的action流, 然后redux 将会依次将新的action流上的节点作用到reducer,去更新state 的值。 换句话说,原本 view -> action -> reducer -> store 的数据流加上中间件后变成了 view -> action -> middleware -> reducer -> store ,因此 Redux 会用中间件来处理异步流。


redux-observable 与很多redux中间件都不同,它是响应式编程的方式,关于响应式编程这个概念,一开始我看到网上一大推糟糕的解释,感觉非常的空泛和理论化,看了还是不是很懂它具体是什么样的方式解决问题,看了一些资料以后,我所理解的就是:响应式编程就是使用异步和数据流的方式,使得有关系的事物之间能够自动响应变化。这种编程方式可以很容易的处理实时消息通知,及时响应变化的这种异步的应用场景。

因为redux-observable是基于RxJS的redux中间件,所以想要使用redux-observable ,也需要对RxJS有所了解。

下面介绍一个redux-observable 常用的关键函数:

Epic 函数

Epic 是一个接收 actions 流 作为参数并返回新的actions流的函数,即Actions 入,Actions 出, 它是redux-observable 的关键函数,redux 会将它返回的actions 通过store.dispatch() 立刻分发给reducers,所以 redux-observable 实际上会做 epic(action$, store).subscribe(store.dispatch)
有一点需要注意的是,在Epics中,虽然是将一个action 映射成另一个action, 但是它不会阻止原始的action 到达 reducers。

epic 用法:

epic($.ofType(type), store)

还有很多RxJS提供的很多构建流的函数,需要用到哪些函数去查就可以了,就不一一介绍,下面提供一个例子来理解和感受redux-observable 是如何实现以 stream 的方式处理异步逻辑。

先来实现一个简单的例子:


现在有一个需求: 需要用户在手机上根据不同的需求场景拍摄 20 张照片,然后在app后台将这 20 张照片 上传到服务器。

分析:根据需求实际上是不影响正常的业务逻辑 ,实现在app 端异步上传照片的过程。


因为业务原因,在前端有有一堆结构很复杂的数据:

const record = {
  "sections": [
    {
      "steps": [
        {
          "images": [
            {
              "imageId": 1
            },
            {
              "imageId": 2
            }
          ],
          "title": "first group images"
        }...
      ],
      "title": "Section-I"
    },
    {
      "steps": [
        {
          "images": [
            {
              "imageId": 3
            },
            {
              "imageId": 4
            }
          ],
          "title": "second group images"
        }...
      ],
      "title": "Section-II"
    }...
  ],
  "title": "Subject"
} 

看起来是一个很复杂的数据结构,现在要在前端实现一张一张的上传里面的每一张照片,如果用很多嵌套 forEach 来做也可以实现,但是可读性非常差,要是这样写,一定会被人怼的很惨,哈哈~
这时候就可以用上基于RxJS的redux-observable 了,看它怎么用流的方式实现这个需求:

const uploadImages = (claimId: number) => ({
  type: 'UPLOAD_RECORD_PHOTOS',
  payload: claimId,
});

export const $uploadImages = (action$, store) => $
  .ofType('UPLOAD_RECORD_PHOTOS')
   .mergeMap({ payload: record } => Observable.of(record)
      .merge(Observable.of(record.sections)
        .mergeMap(section => section.steps)
        .mergeMap(step => step.images)
        .mergeMap(image => api.backgroundUploadFile({
          url: URL.record.uploadImage(image.recordId, image.imageId),
          filePath: Platform.OS === 'ios' ? `file://${image.uri}` : image.uri,
          fileKey: 'file',
        })
          .map(() => uploadRecordImageCompleted(
             _.get(store.getState().entities.records,recordId), 
            image.imageId,
            imageUploadStatus.uploadSucceed))
          .catch(() => Observable.of(uploadRecordImageCompleted(
             _.get(store.getState().entities.records, recordId), 
            image.imageId,
            imageUploadStatus.uploadFailed)))))));

const uploadRecordImageCompleted = (
  record, photoId, status
) => {
  const record = _.cloneDeep(record);
  _.forEach(record.sections, section =>
    _.forEach(section.steps, step =>
      _.forEach(step.images, (image) => {
        if (image.imageId === imageId) {
          _.set(image, 'uploadStatus', status);
        }
      })));
  return updateRecord(record.recordId, record);
};

 const updateRecord = (recordId: number, payload: any) => ({
  type: 'UPDATE_ENTITIES',
  payload: { records: { [recordId]: payload } },
});

我们看一下这段代码都做了什么事情?
当某处调用了action 方法 uploadImages(record) ,就会触发对应的 action={ type: 'UPLOAD_RECORD_PHOTOS', payload: record, },即执行我们写的这个方法,分析方法执行过程如下:

首先我们看到方法 uploadImages(action$, store) 第一个参数就是入参 action 流,store 就是我们redux中的 state 存储集,当触发 action:UPLOAD_RECORD_PHOTOS 就会执行该方法,该放中做了action过滤,.ofType('UPLOAD_RECORD_PHOTOS') 表示传入action流 如果是UPLOAD_RECORD_PHOTOS 这种action就做如下的流转换处理,如果不是该action, 就什么也不做。

如果触发了 action UPLOAD_RECORD_PHOTOS 就会执行该方法 uploadImages(action$, store) 。 可以看到接下来的 mergeMap 会将参数中的record 由Observable.of(record) 将其创建成observable,然后合并所有的observables 成一个流 ,如下:
.merge(Observable.of(record.sections)会将多个由Observable.of(record.sections) 创建的Observable 合并成一个Observable,并将其展开形成流的形式。

.mergeMap(section => section.steps)会将每个 section 中的 多个 steps 展开形成流的形式,如下:

.mergeMap(step => step.images)会将 每个 step 中的多个 images 展开形成流的形式,如下:

image.png

.mergeMap(photo => api.backgroundUploadFile() 会根据每个 imageid 调用一个 异步上传文件的api, 并返回一个api执行的状态结果,如下:

image.png

最后根据每个上传 image 的api 状态结果,转化为对应的action,就会形成一个新的action流,该 action 流上的每个节点都是一个action,每个 action 的作用是更新本地每个 image 的上传状态,以便于知道是否每个 image 都已将成功上传,然后能够再次重新上传那些上传失败状态的image。
整个过程会将原来action 流转化成新的action流的形式,如下:

最后生成的新的 action 流,相当于是在原来action 节点处扩展产生多个新的action节点, 然后redux 将会依次将新的action流上的节点作用到reducer,去更新state 的值。

其实这样看起来就清晰多了,在这个例子中, redux-observable 件会将一个结构复杂的逻辑处理转化成流的方式,实现了我们的需求:将一个结构化数据里面的每张照片都上传并记录它们的上传结果的状态,并且这个上传照片的动作是异步的,就不会阻碍其他正常的业务流程就继续正常运行。

在这里,根据我对redux-observable 的理解,做了简单的分析,如果文中有什么问题,希望帮助我指出来,谢谢~

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

推荐阅读更多精彩内容

  • http://gaearon.github.io/redux/index.html ,文档在 http://rac...
    jacobbubu阅读 79,887评论 35 198
  • Angular2和Rx的相关知识可以看我的Angular 2.0 从0到1系列 第一节:初识Angular-CLI...
    接灰的电子产品阅读 22,645评论 39 59
  • 学习必备要点: 首先弄明白,Redux在使用React开发应用时,起到什么作用——状态集中管理 弄清楚Redux是...
    贺贺v5阅读 8,875评论 9 58
  • 使用redux+react已有一段时间,刚开始使用并未深入了解其源码,最近静下心细读源码,感触颇深~ 本文主要包含...
    字节跳动技术团队阅读 1,456评论 0 5
  • 到底是生活束缚了我们? 亦或是我们捆绑住生活? 思虑总在远方,内心满怀憧憬。 发着呆,偶尔会心一笑。 绷着脸,总有...
    归尘于土阅读 170评论 0 0