s
s
saga-duck
Search…
介绍&入门

saga-duck是用来做什么的?

一 句话:它是基于ducks模式思想,实现了模块化、可复用、可扩展及可组合特性的redux-saga开发方案。
在使用Redux时,我们发现业务逻辑过于分散,没有模块化,于是决定使用ducks模式来管理代码。
因为业务交互复杂,使用了redux-saga来管理逻辑,这就需要duck能支持redux-saga,并可以扩展和组合使用。extensible-duck 是个很不错的方案,但是它没有考虑支持redux-saga,并且组合使用不太方便,于是我们便重新造一个轮子。

实现可复用

通常Redux的actionType都定义为常量,是固定不变的。但如果要复用,在同一个Redux store中就会冲突了,于是和extensible-duck一样最终的duck实例会根据路径的不同,生成不同的actionType(例如 namespace/route/ADD)。
actionType不同了,理所当然相关的reducer和actionCreator也会不同,都是动态生成。
1
import {Duck} from 'saga-duck'
2
class MyDuck extends Duck{
3
// duck.types会从它来自动生成
4
get quickTypes(){
5
return {
6
...super.quickTypes,
7
ADD: 1
8
}
9
}
10
get reducers(){
11
// types是由quickTypes自动生成的,如果使用typescript,会自动进行提示
12
const { types } = this
13
return {
14
...super.reducers,
15
num: (state=0, action)=>{
16
switch(action.type){
17
case types.ADD:
18
return state+1
19
default:
20
return state
21
}
22
}
23
}
24
},
25
get creators: (){
26
return {
27
...super.creators,
28
add(){
29
return {type: types.ADD}
30
}
31
}
32
}
33
}
Copied!

支持redux-saga

在参考extensible-duck后,我们添加了可扩展的saga和selectors

saga

saga-duck 3.x中,duck自身有一个generator成员`saga`,可以直接扩展它

selector/selectors

通常我们都是比较暴力地访问store,而在模块化后,你并不确定Duck被用在哪个位置,所以在saga中访问状态需要通过selector/selectors进行
1
const state = duck.selector(yield select()) // 获取当前duck对应的state
2
const xxx = duck.selectors.xxx(yield select()) // 获取对应的selectors的值
3
4
// 注意,这里我们没有用下面被注释掉的写法(它才是redux-saga推荐的)
5
// 是因为typescript的限制,在yield后我们丢失了类型信息。
6
// 用上面的写法,所有类型都在,可以方便地进行编码。
7
8
// yield select(duck.selector)
9
// yield select(duck.selectors.xxx)
Copied!
于是完整的带saga逻辑的duck就是这样
1
import {Duck} from 'saga-duck'
2
class MyDuck extends Duck{
3
get quickTypes...
4
get reducers...
5
get creators...
6
get rawSelectors(){
7
return {
8
...super.rawSelectors(),
9
num: state => state.num
10
}
11
}
12
*saga(){
13
yield* super.saga()
14
const {types, selectors, selector, creators} = this
15
yield takeEvery(types.ADD, function*(add){
16
const state = selector(yield select()) // { num: 1 }
17
const num = selectors.num(yield select()) // 1
18
yield put(creators.add())
19
})
20
}
21
}
Copied!

可扩展

从前面的例子大家已经可以看到,我们是从Duck开始继承的,当然我们还可以继续继承扩展。
1
class AnotherDuck extends MyDuck{
2
get reducers()...
3
get quickTypes()...
4
*saga()...
5
}
Copied!

可组合

在最终用到Redux store上时,我们需要把ducks按路径拼装,比如我们实现了一个列表交互ListDuck,现在界面上有两个列表,那么可以这样组合成一个Duck
1
import { DuckMap } from 'saga-duck'
2
3
class MyDuck extends DuckMap{
4
get quickDucks(){
5
return {
6
...super.quickDucks,
7
list1: ListDuck,
8
list2: ListDuck
9
}
10
}
11
*saga(){
12
const {ducks:{list1, list2}} = this
13
const list1Data = list1.selectors.list(yield select()) // 列表1的数据
14
}
15
}
Copied!
注: DuckMap继承自Duck,有它的一切功能。

使用到React上

最终所有业务可以组合到一个RootDuck上,然后可以通过DuckRuntime来自动执行起来,并便捷地与React组件关联起来
1
import { Provider } from 'react-redux'
2
import { DuckRuntime } from 'saga-duck'
3
const rootDuck = new RootDuck()
4
const duckRuntime = new DuckRuntime(rootDuck)
5
// 也可以使用decorator快速定义 @duckRuntime.connectRoot()
6
const ConnectedContainer = duckRuntime.connectRoot()(Container)
7
8
ReactDOM.render(
9
<Provider store={duckRuntime.store}>
10
<ConnectedContainer />
11
</Provider>,
12
document.getElementById('root')
13
)
Copied!
Container中可以这样访问Redux store及duck相关内容
1
function Container(props){
2
const { duck, store, dispatch } = props
3
const { selectors, creators, ducks: { list1, list2 } } = duck
4
// const xxx = selectors.xxx(store)
5
// const list1Data = list1.selectors.list(store)
6
}
Copied!
注意:建议所有与duck相关的React组件统一使用约定的 { duck, store, dispatch } 格式来传递到子组件,方便我们后续进行简单的性能优化。详情请看 进阶

支持Typescript

2.0版我们引入了typescript,在VS Code下可以做到比较方便的代码提示及纠错,但只支持到typescript 2.6.1
3.0版我们对Duck的定义方法进行了大改,由泛型+Options改为全getter方式,您无需显式地声明任何类型,一切类型都会自动识别生成。但注意只支持typescript3.0+
更多的对比请看这里
Last modified 2yr ago