redux-saga源码学习
/ / 点击 / 阅读耗时 26 分钟上一篇整明白了redux-saga的原理,现在来具体看看代码实现。
讲redux-saga源码的资料真是少的可怜,英文完全没搜到…中文就搜到两篇有价值的,第二篇对整个redux-saga的代码实现做了比较详细的描述。
然而现在的水准做不到完全掌握,能学多少是多少。
辅助理解的变量名
cont
continuation 缩写,一般用于表示 Task / MainTask / ForkQueue 的后继
cb
callback 缩写 或是 currCb 应该是 currentCallback 的缩写。一般用于 effect 的后继/回调函数
next
就是前边的递归函数
fork model
parent task: the parent tasks is the aggregation of the main tasks + all its forked tasks
main task: main task is the main flow of the current Genarator
主线
- 关联redux和redux-saga
使用redux中的applyMiddleware将redux中的dispatch注入到redux-saga的sagaMiddleware中
redux的applyMiddleware文件中的这几行代码完成了主要工作:
1 | const middlewareAPI = { |
下面是使用redux和redux-saga关联的实际使用示例代码:
1 | import { createStore, applyMiddleware } from 'redux' |
- middleware.js
关联后,控制权移入sagaMiddleware函数中,接入redux-saga最终要的一行代码是:channel.put(action)
,这行代码背后的含义是当出现action的操作时,会执行提前绑定好的回调函数。
1 | function sagaMiddleware({ getState, dispatch }) { |
- channel.js
这个文件中的部分源码如下:
1 | export function multicastChannel() { |
- runSaga.js
sagaMiddleware.run(helloSaga)
是在启动Generator函数,这里的boundRunSaga是一个偏函数,是绑定了一堆基本参数的runSaga:
1 | // ... |
其中的runSaga出自runSaga.js文件。
runSaga函数的最后是下面这几行代码,其中调用proc函数,启动了saga,生成了一个task并返回:
1 | return immediately(() => { |
首先看一下immediately函数,这个函数位于scheduler.js文件中:
1 | export function immediately(task) { |
- proc.js
proc函数的主要代码如下:
1 | function proc(env, iterator, parentContext, parentEffectId, meta, isRoot, cont) { |
- newTask.js
在saga的启动函数proc函数中,会创建一个task对象,
下面是创建task对象的主要代码:
1 | // newTask创建的对象用途和上面的mainTask类似 |
- forkQueue.js
forkQueue是fork模型的实现,感觉代码实现很直观。
代码如下:
1 | function forkQueue(mainTask, onAbort, cont) { |
- resolvePromise.js
proc函数中的runEffect函数中的第一种情况是用来处理promise,比如发ajax请求数据,处理promise使用了resolvePromise,代码如下,也很直观:
1 | function resolvePromise(promise, cb) { |
- io.js
runEffect函数中的第三种请求是如果判断传入的参数是effect,则执行effectRunner,先看下effect的工厂函数。
1 | const makeEffect = (type, payload) => ({ |
makeEffect返回一个纯对象,对象的属性包含了相关信息用于在effectRunner中执行。
call、put、take、fork、all等effect都是利用在makeEffect构造的。
- effectRunnerMap.js
这个函数中包含了各种effect的实际执行环境函数,以runForkEffect函数为例:
1 | function runForkEffect(env, { context, fn, args, detached }, cb, { task: parent }) { |
相比下面的runTakeEffect函数,runForkEffect会起一个新的saga,新的saga中有自己的next迭代函数,每一个fork分支对应一个,所以不存在阻塞的问题(这里的阻塞感觉是相对Generator的主执行环境而言,这里没有看到有起其他的工作线程,相对于主线程而言还是要按顺序线性执行)。但其他的effect,比如take,是运行在mainTask所在的那个next中,就出现了等待,常用的call也是这样。另外使用take会使middleware等待出现一个特定的action(其实看源码是通过类型判断的,并没有具体到某一个)这是因为将next传入了channel的队列中,成为了channel的回调,看起来像是middleware的等待。
1 |
|
卤煮
is
文件中有20种对不同类型的判断,感觉多数都用了鸭子类型,鸭子类型牛逼。once
,这个函数出自utils
文件,用来确保一个函数只执行一次,在underscore中也有这个工具函数,但好像写过去就过去了,实际开发中没有用到过,现在拿出来再熟悉一下,用闭包实现的,很好玩:1
2
3
4
5
6
7
8
9
10function once(fn) {
let called = false
return () => {
if (called) {
return
}
called = true
fn()
}
}
参考资料
- https://zhuanlan.zhihu.com/p/30098155 (这篇文章的前半部分介绍的原理,后半部分举例了fork这个effect的实现和takeEvery这个帮助函数的实现)
- https://zhuanlan.zhihu.com/p/37356948 (这篇文章讲的很好,只是结构有问题,感觉分块儿来讲第一次看起来一头雾水,但瑕不掩瑜)