type
Post
status
Published
date
May 1, 2020
slug
summary
这是去年写的一篇总结了,一直没搬到我的博客上来,最近才想起。
tags
Web dev
category
技术分享
icon
password
这是去年写的一篇总结了,一直没搬到我的博客上来,最近才想起。
问题:
- 使用最新的请求结果
- 取消一个耗时很长的请求(手动/超时自动)
- 轮询(循环定时发起)
- 排队请求(循环排队发起)
- 跨 model 时序问题
分析:
一.redux-saga
1.redux-saga是个中间件
那源码是否可以找到 redux-thunk 这样的中间件写法?(redux-thunk 源码简析之前文章做过了,14 行代码很简单)
可以,就在 /packages/core/src/internal/middleware.js(0.16.0 src/internal/middleware.js)
sagaMiddlewareFactory 这个函数等同于 createThunkMiddleware
其主要逻辑是
return next => action => { if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } const result = next(action) // hit reducers channel.put(action) return result }
使用时,对比thunk:
// thunk const store = createStore( rootReducer, applyMiddleware(thunk) );
// saga const sagaMiddleware = createSagaMiddleware() createStore(rootReducer, applyMiddleware(sagaMiddleware)) sagaMiddleware.run(rootSaga)
上面的 thunk 中间件这样用以后,变化的是 reducer,本来 action 是一个 plainObject,现在的 action 可以是个方法了;而用了 saga 中间件以后,之前的 reducer 没有变化,action 还是一个 plainObject 表示 state 的变化,而异步的都放在 sagas(上面的 rootSaga)中处理了,至于 channel.put(action) 可以理解成 channel 是一个发布订阅模式,channel.put 就是发布,而 saga 中会订阅(take)
也就是说 saga 的程序逻辑会存在两个位置:
- Reducers 负责处理 action 的 state 转换
- Sagas 负责策划统筹合成和异步操作
源码中也能体现
const result = next(action)
,不像 thunk 还做了拦截处理,而 saga 的处理其实是在 run 函数, run 函数源码对应 runSaga/packages/core/src/internal/runSaga.jsconst iterator = saga(...args)
这里 saga 的 args 参数可能有各种情况,本例 saga 对应 rootSaga,
...args
是空的。确实,rootSaga 是一个 generator,他执行后返回一个 IterableIterator 类型的对象 iterator
export default function* root() { yield all([ fork(getAllProducts), fork(watchGetProducts), fork(watchCheckout) ]) }
那我们看看怎么处理这个 iterator 的
return immediately(() => { const task = proc(env, iterator, context, effectId, getMetaInfo(saga), /* isRoot */ true, noop) if (sagaMonitor) { sagaMonitor.effectResolved(effectId, task) } return task})
作为参数传给了 proc 函数,外面包的这层函数先不用管,这里可以看成
(()=>{proc})()
一个立即执行函数,再看看 proc,这是重点了 run(rootSaga)
执行的调用栈(从proc开始,前面的都分析了) proc()
- >
next()
- >
result = iterator.next(arg)
- (看过 generator 应该知道此时来到了第一个yield all()
, 打印一下{value:{type: ALL,....},done: false}
,判断 done 还不为 true,直接打印console.log(all());
也会看到这个结构)
- >
digestEffect()
- >
finalRunEffect()
- (finalRunEffect()是在runSaga中封装了runeffect,runsaga可以接受一个effectMiddlewares参数进行封装TODO1)
- >
runEffect()
- > 然后去 effectRunnerMap.js
- >
runAllEffect()
-> forEach 调用digestEffect()
同上第一个是 fork ->runForkEffect()
-> proc
当前 iterator 就变成了 getAllProducts
我们主要关注 redux-saga 的使用与 dva 的原理和实现
只需强调几个地方帮助我们理解: 1.watch-and-fork 模式 2.阻塞(take,call…)与非阻塞(fork,put…) 3.saga-helper
2.dva 封装了啥
dva 调用 run
store.runSaga = sagaMiddleware.run;
injectModel 中 store.runSaga
参数应该是 rootSaga,那 dva 中是啥 是一个 getSaga 方法 dva/packages/dva-core/src/getSaga.js 返回是一个 funtion*() 就像 rootSaga 一样但里面没有用 all() 包装,而是一个 for 循环,毕竟 all() 中也是 forEach,然后const watcher = getWatcher(key, effects[key], model, onError, onEffect, opts);const task = yield sagaEffects.fork(watcher);
第一行每一个 saga 我们都用一个 sagaHelper 包装一下
第二行包装后的 saga 我们 fork 一下,就像前面的例子一样
这里就需要理解一下 sagaHelper 和 fork 的意义了
fork 是非阻塞的 call(https://redux-saga-in-chinese.js.org/docs/api/)
另外,
createEffects return { ...sagaEffects, put, take };
这里可以发现,dva 中是可以使用 redux-saga 中所有的 effects 的,不过要注意版本3.saga helper(saga辅助函数)
saga helper 对应源码 io-helpers
debounce
, retry
, takeEvery
, takeLatest
, takeLeading
, throttle
,saga 辅助函数是由effect创建器组合而来的高级APItakeEvery
是一个使用 take
和 fork
构建的高级 API。下面演示了这个辅助函数是如何由低级 Effect 实现的const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() { while (true) { const action = yield take(patternOrChannel) yield fork(saga, ...args.concat(action)) }})
dva 的
"redux-saga": "^0.16.0"
,包含的 saga helper 只有 takeEvery
, takeLatest
, throttle
,对应 sagaHelpers 这个目录dva 中的 saga helper 作为 effects type 存在
getWatcher 源码中默认用 takeEvery 包裹,所以一般没有写,问题 3 就可以用takeLatest来解决
我们具体来看看每一个 saga helper 的用途
1).先看看 redux-saga 本身的
takeEvery: 每次 dispatch action 都会触发
takeLatest: 每次 dispatch action 会销毁之前的,再触发
throttle: 节流,不多解释
2).dva依赖redux-saga@0.16.x,下面这些新的saga-helper你用dva时应该没有见过:
takeLeading: 每次dispatch触发一次,当执行完了才能在被触发,中间dispatch不会触发
debounce: 防抖,不多解释
retry: 失败自动重试
3).另外由于 saga helper 就是简单的 effects 创建器组合而成的高级API,所以 dva 在封装时也自己做了几个 saga helper(在dva里叫effect type)
watcher: 其实就就是没有用 saga-helper,初始化启动,这个名字来源是由于 saga 常见模式 watch-and-fork 模式,dva 只对其做了简单的
try catch
封装poll(2.6beta版): 顾名思义是做轮询用的(解决了开头的问题),现在我们用的是 dva@2.4 版本还没有这个 effect,可以看下他怎么做的,总之是由简单的 effect 创建器组合而成,solvay 中就自己实现过
4.effect 创建器
作用:为了方便测试,把直接调用改为使用 effect 创建器包装调用
take、fork
5.channel
…
本文相关代码 https://github.com/wave52/react-practise/blob/master/src/redux/dvaApp/index.js
- 作者:Wave52
- 链接:https://vercel.wuchengran.com/article/fe898dfd-6201-421c-a503-e35e8454ffd0
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章