理解为什么而用?
前阵子手撕了部分redux
源码,对其中一些设计思想的理解提升了不少。但是,在我自身开发的历程中还是一直有一个问题困扰着我:这些不断进化的redux
异步中间件库到底是为什么出现的,它们的诉求究竟是解决一个什么样的问题?
redux-thunk
redux-thunk
是我最早接触的一个异步中间件库。它解决的核心诉求是:如何统一组织异步场景的action
派发,用白话文来说就是统一在一个地方(文件目录下)进行异步action
逻辑的封装。在之前<<论Redux异步流>>一文中,我们讨论过本身action
进行dispatch
时,是一个同步的动作。而redux-thunk
中间件支持了action
为函数类型的传值,这也使我们能够在这个action
函数内部进行异步逻辑控制,最后在回调中同步dispatch
数据。
我们知道在redux-thunk
中,对action
函数的处理如下:
1 | if (typeof action === 'function') { |
于是我们会在如action
这个目录下进行对应业务逻辑的异步封装。下面以请求一个列表页数据为例:
1 | const FETCH_TABLE_LIST = 'FETCH_TABLE_LIST'; |
假如我们没有使用redux-thunk
之类的中间件进行逻辑的集中管理(使dispatch
接受function
、promise
等类型的action
),那我们上述的一些副作用就会散步在各个业务组件中,就以我们前文中的请求列表数据来说:
1 | // 业务组件 xx.js |
看上去其实好像也没什么不同,这实际上就是一个将代码放在哪里管理进行复用的问题。对比redux-thunk
封装后调用的写法是:this.props.dispatch(fetchTableList(params))
,我们通过接受函数类型的action
,使得在业务组件中的dispatch
呈现显然更加清晰,副作用也不会被暴露在业务组件中。
redux-promise
与thunk
差不多,只不过接收的是Promise
,它的诉求估计在于不用在业务组件层写那么多this.props.dispatch(fetchTableList(params)).then().catch()
之类的代码。
瞅瞅源码:
1 | import { |
大意就是,先判断是否是一个标准的action
,如果不是,则判断是不是Promise
类型的,是则在resolved
后dispatch
,不是则正常走中间件的next
步骤。如果是一个标准action
,则对其payload
内容进行判断(是否是Promise
),其中进行成功和失败情况下的dispatch
。
还是拿我们之前获取列表的场景,结合官方提供的用例有:
1 | import { createAction } from 'redux-actions'; |
注,一个标准的action
具有type
、payload
、error
、meta
四个key
,createAction
能帮我们生成一个标准的FSA对象。像上面的请求API会被赋值到action.payload
下。自然也会走中间件内action.payload
的Promise.then.catch
流程。
redux-saga
saga
,我认为它带来的核心价值有二:其一是使用generator
语法处理了异步回调地狱问题;其二是使用混合式(命令+声明)编程的思想组织异步数据流,在具体异步逻辑中采用命令式编程,然后声明式调用派发action
,触发saga
的effects
。
通过阅读saga
库的README.md
,我们先理清其提供的几个API的基本功能:
put
: 相当于数据派发时的dispatch
;call
: 配合yield
,调用API,传入参数;takeEvery
: 类似观察者模式中观察者的角色,当我们触发dispatch
时,对应type
的action
会调用对应的generator
函数;takeLatest
: 是takeEvery
的一种可替代方案,调用方式相同,不过如果同一时间还有别的请求存在(比如网络问题导致的pending
),旧的将会被取消,即只有最新的调用请求会保留;
话外
分享一篇stackoverflow上关于redux middleware的讨论文章 Why do we need middleware for async flow in Redux?