redux-saga源码分析(一)
Opened this issue · 0 comments
Rashomon511 commented
简述
redux-saga就是用于管理副作用如异步获取数据,访问浏览器缓存的一个中间件。其中reducer负责处理state更新,sagas负责协调异步操作,并提供一系列的API(take,put,call等)让副作用管理更容易,执行更高效,测试更简单。
源码分析
由于是redux的异步扩展,redux-saga中广泛应用了redux中的很多函数,比如applyMiddleware、dispatch、getState等。如对redux不熟悉,建议看下redux源码分析,我们会通过该例子来分析redux-sgag源码
内部执行逻辑
入口文件
在store/index.js中通过createSagaMiddleware和sagaMiddleware.run(rootSaga)引入redux-saga逻辑。sagaMiddleware.run其实调用的是runsage。
// 这是redux-saga中间件的入口函数,是按照中间件的基本编写方式来写的
// context、options默认是空的,分析的时候可以忽略
function sagaMiddlewareFactory({ context = {}, ...options } = {}) {
const { sagaMonitor, logger, onError, effectMiddlewares } = options
let boundRunSaga
// 下面就是中间件基本的编写方式
// sagaMiddleware.run其实调用的是runsage
function sagaMiddleware({ getState, dispatch }) {
const channel = stdChannel()
channel.put = (options.emitter || identity)(channel.put)
// 为runSaga提供redux的函数以及subscribe
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)
channel.put(action)
return result
}
}
// 负责启动中间件的函数,下一小节讲述
sagaMiddleware.run = (...args) => {
return boundRunSaga(...args)
}
sagaMiddleware.setContext = props => {
assignWithSymbols(context, props)
}
return sagaMiddleware
}
runSaga函数
你传入的saga函数是一个generator函数。
function runSaga(options, saga, ...args) {
// saga就是传过来的saga函数
const iterator = saga(...args)
const {
channel = stdChannel(),
dispatch,
getState,
context = {},
sagaMonitor,
logger,
effectMiddlewares,
onError,
} = options
const effectId = nextSagaId()
// 日志
const log = logger || _log
const logError = err => {
log('error', err)
if (err && err.sagaStack) {
log('error', err.sagaStack)
}
}
// 是否有effectMiddlewares
const middleware = effectMiddlewares && compose(...effectMiddlewares)
const finalizeRunEffect = runEffect => {
if (is.func(middleware)) {
return function finalRunEffect(effect, effectId, currCb) {
const plainRunEffect = eff => runEffect(eff, effectId, currCb)
return middleware(plainRunEffect)(effect)
}
} else {
return runEffect
}
}
const env = {
stdChannel: channel,
dispatch: wrapSagaDispatch(dispatch),
getState,
sagaMonitor,
logError,
onError,
finalizeRunEffect,
}
try {
suspend()
// 这一行是最终执行的
const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)
if (sagaMonitor) {
sagaMonitor.effectResolved(effectId, task)
}
return task
} finally {
flush()
}
}
oasp函数
asap是一个调度策略,存放了一个quene,然后每次只允许一个任务执行
const queue = []
let semaphore = 0
function exec(task) {
try {
suspend()
task()
} finally {
release()
}
}
export function asap(task) {
queue.push(task)
if (!semaphore) {
suspend()
flush()
}
}
export function suspend() {
semaphore++
}
function release() {
semaphore--
}
// while循环,将队列中执行完成,直到为空
export function flush() {
release()
let task
while (!semaphore && (task = queue.shift()) !== undefined) {
exec(task)
}
}
stdChannel函数
stdChannel函数运行时才去运行multicastChannel,最终返回multicastChannel的运行结果,该结果为一个对象
function stdChannel() {
const chan = multicastChannel()
const { put } = chan
chan.put = input => {
// SAGA_ACTION :一个字符串,模版字符串 `@@redux-saga/${name}`
if (input[SAGA_ACTION]) {
put(input)
return
}
// asap是一个调度策略,存放了一个quene,然后每次只允许一个任务执行
asap(() => {
put(input)
})
}
return chan
}
上面函数中的chan是multicastChannel函数执行的结果,返回了一个对象,对象包含put、take方法
- take方法:将回调函数存入nextTakers
- put方法:执行相应的回调函数
function multicastChannel() {
let closed = false
// 在状态管理中,经常碰到current和next的操作,为了保持一致性
// 一个代表当前状态(�任务队列),
// 一个代表下一个状态(任务队列),
// 初始状态两个是一致的
let currentTakers = []
let nextTakers = currentTakers
// 下面函数做的操作是,将当前的队列,复制给下一个队列
const ensureCanMutateNextTakers = () => {
if (nextTakers !== currentTakers) {
return
}
nextTakers = currentTakers.slice()
}
const close = () => {
closed = true
const takers = (currentTakers = nextTakers)
nextTakers = []
takers.forEach(taker => {
// END是一个对象,END = { type: CHANNEL_END_TYPE }
taker(END)
})
}
return {
[MULTICAST]: true,
put(input) {
if (closed) {
return
}
// isEND是一个函数,判断是不是已经结束了
// isEnd = a => a && a.type === CHANNEL_END_TYPE
if (isEnd(input)) {
close()
return
}
const takers = (currentTakers = nextTakers)
for (let i = 0, len = takers.length; i < len; i++) {
const taker = takers[i]
if (taker[MATCH](input)) {
taker.cancel()
taker(input)
}
}
},
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,
}
}