关于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
展开形成流的形式,如下:
.mergeMap(photo => api.backgroundUploadFile()
会根据每个 image
的 id
调用一个 异步上传文件的api, 并返回一个api执行的状态结果,如下:
最后根据每个上传 image 的api 状态结果,转化为对应的action,就会形成一个新的action流,该 action 流上的每个节点都是一个action,每个 action 的作用是更新本地每个 image 的上传状态,以便于知道是否每个 image 都已将成功上传,然后能够再次重新上传那些上传失败状态的image。
整个过程会将原来action 流转化成新的action流的形式,如下:
最后生成的新的 action 流,相当于是在原来action 节点处扩展产生多个新的action节点, 然后redux 将会依次将新的action流上的节点作用到reducer,去更新state 的值。
其实这样看起来就清晰多了,在这个例子中, redux-observable 件会将一个结构复杂的逻辑处理转化成流的方式,实现了我们的需求:将一个结构化数据里面的每张照片都上传并记录它们的上传结果的状态,并且这个上传照片的动作是异步的,就不会阻碍其他正常的业务流程就继续正常运行。
在这里,根据我对redux-observable 的理解,做了简单的分析,如果文中有什么问题,希望帮助我指出来,谢谢~