Redux-saga是redux应用的又一个副作用模型。可以用来替换redux-thunk中间件。
redux-saga 抽象出 Effect (影响, 例如等待action、发出action、fetch数据等等),便于组合与测试。
我想在分析redux-saga之前,先来看看redux-thunk是怎么一回事
redux 在我之前一篇文章中讲过了链接
那我们就先用 redux-thunk 来写一个 asyncTodo 的demo
redux-thunk 分析
1 | import { createStore, applyMiddleware } from 'redux'; |
原本redux中action只能是 plain object ,redux-thunk使action可以为function。当我们想抛出一个异步的action时,其实我们是把异步的处理放在了actionCreator中。
这样就会导致action形式不统一,并且对于异步的处理将会分散到各个action中,不利于维护。
接下来看看redux-saga是如何实现的
redux-saga
1 | import { createStore, applyMiddleware } from 'redux'; |
可以看到这里抛出的就是一个纯action, saga在启动之后监听 ADD_TODO_SAGA 事件,若事件发生执行后续代码。
源码
stdChannel
在开始createSagaMiddleware之前,先来了解一下 channel
redux-saga 通过 channel 接收与发出action与外部进行数据交换
在redux-saga中有三种 channel,分别是channel、eventChannel、multicastChannel;
在此我们仅仅分析一下用的最多的 multicastChannel1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80export function multicastChannel() {
let closed = false
// 这里taker分为的currentTakers、 nextTakers的原因和redux subscribe类似,防止在遍历taker时,taker发生变化。
let currentTakers = []
let nextTakers = currentTakers
const ensureCanMutateNextTakers = () => {
if (nextTakers !== currentTakers) {
return
}
nextTakers = currentTakers.slice()
}
const close = () => {
closed = true
const takers = (currentTakers = nextTakers)
for (let i = 0; i < takers.length; i++) {
const taker = takers[i]
taker(END)
}
nextTakers = []
}
return {
[MULTICAST]: true,
put(input) {
if (closed) {
return
}
if (isEnd(input)) {
close()
return
}
const takers = (currentTakers = nextTakers)
// 遍历takers,找到与input匹配的taker并执行它。
for (let i = 0; i < takers.length; i++) {
const taker = takers[i]
if (taker[MATCH](input)) {
taker.cancel()
taker(input)
}
}
},
// 存下callback,与配置函数
take(cb, matcher = matchers.wildcard) {
if (closed) {
cb(END)
return
}
cb[MATCH] = matcher
ensureCanMutateNextTakers()
nextTakers.push(cb)
cb.cancel = once(() => {
ensureCanMutateNextTakers()
remove(nextTakers, cb)
})
},
close,
}
}
export function stdChannel() {
const chan = multicastChannel()
const { put } = chan
chan.put = input => {
if (input[SAGA_ACTION]) {
put(input)
return
}
// 暂时不用管
asap(() => put(input))
}
return chan
}
createSagaMiddleware
获取redux-middleware, 同时初始化runsaga函数,为后面启动saga bind 所需的参数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) {
const { sagaMonitor, logger, onError, effectMiddlewares } = options
let boundRunSaga
// redux middleware
function sagaMiddleware({ getState, dispatch }) {
// 新建一个channel
const channel = stdChannel()
channel.put = (options.emitter || identity)(channel.put)
boundRunSaga = runSaga.bind(null, {
context,
channel,
dispatch,
getState,
sagaMonitor,
logger,
onError,
effectMiddlewares,
})
return next => action => {
if (sagaMonitor && sagaMonitor.actionDispatched) {
sagaMonitor.actionDispatched(action)
}
const result = next(action) // hit reducers
// 将事件传递给saga
channel.put(action)
return result
}
}
// 启动saga
sagaMiddleware.run = (...args) => {
// ...
return boundRunSaga(...args)
}
//...
return sagaMiddleware
}
runsaga
1 | export function runSaga(options, saga, ...args) { |
redux-saga的核心就是task, 控制generator函数saga执行流程。是一个复杂的自动流程管理,我们先看一个简单的自动流程管理1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// 一个返回promise的delay函数
const delay = (ms) => {
return new Promise((res) => {
setTimeout(res, ms);
});
}
function *main() {
yield delay(1000);
console.log('1s later');
yield delay(2000);
console.log('done');
}
// 为了达到想要的执行结果,我们必须在promise resolved之后再执行next statement,比如这样
const gen = main();
const r1 = gen.next();
r1.value.then(() => {
const r2 = gen.next();
r2.value.then(() => {
gen.next();
})
})
使用递归实现,自动流程控制1
2
3
4
5
6
7
8
9
10
11
12function autoRun(gfunc) {
const gen = gfunc();
function next() {
const res = gen.next();
if (res.done) return;
res.value.then(next);
}
next();
}
autoRun(main);
上面的自动流程控制函数仅仅支持 promise。
proc
1 | export default function proc(env, iterator, parentContext, parentEffectId, meta, cont) { |
总结
redux-saga 将异步操作抽象为 effect,利用 generator 函数,控制saga流程。
到目前为止,只是涉及了一些基本流程,下一篇会对本篇进行补充。