type
Post
status
Published
date
May 1, 2020
slug
summary
这是去年写的一篇总结了,一直没搬到我的博客上来,最近才想起。
tags
Web dev
category
技术分享
icon
password
这是去年写的一篇总结了,一直没搬到我的博客上来,最近才想起。

问题:

  1. 使用最新的请求结果
  1. 取消一个耗时很长的请求(手动/超时自动)
  1. 轮询(循环定时发起)
  1. 排队请求(循环排队发起)
  1. 跨 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.js
const 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 的意义了
另外,createEffects return { ...sagaEffects, put, take }; 这里可以发现,dva 中是可以使用 redux-saga 中所有的 effects 的,不过要注意版本

3.saga helper(saga辅助函数)

saga helper 对应源码 io-helpers debounceretrytakeEverytakeLatesttakeLeadingthrottle,saga 辅助函数是由effect创建器组合而来的高级API
takeEvery 是一个使用 takefork 构建的高级 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 只有 takeEverytakeLatestthrottle,对应 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
前端路由ESLint Checklist