blog
blog copied to clipboard
redux-saga源码学习
前言
好奇redux-saga是如何实现副作用的流程的,下面一起来看看源码。
注意:本次阅读redux-saga源码版本是1.1.3
示例demo
main.js
import * as React from 'react'
import ReactDOM from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import Counter from './components/Counter'
import reducer from './reducers'
import rootSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(reducer, applyMiddleware(sagaMiddleware))
sagaMiddleware.run(rootSaga)
const action = type => store.dispatch({ type })
function render() {
ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => action('INCREMENT')}
onDecrement={() => action('DECREMENT')}
onIncrementIfOdd={() => action('INCREMENT_IF_ODD')}
onIncrementAsync={() => action('INCREMENT_ASYNC')}
/>,
document.getElementById('root'),
)
}
render()
store.subscribe(render)
reducer.js
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'INCREMENT_IF_ODD':
return (state % 2 !== 0) ? state + 1 : state
case 'DECREMENT':
return state - 1
default:
return state
}
}
saga.js
import { put, takeEvery, delay } from 'redux-saga/effects'
export function* incrementAsync() {
yield delay(1000)
yield put({ type: 'INCREMENT' })
}
export default function* rootSaga() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
源码调试
注册中间件
执行createSagaMiddleware
方法。
在sagaMiddleware中通过next(action)
命中reducer逻辑,然后再执行channel.put
,执行action对应的回调函数
export default function sagaMiddlewareFactory({ context = {}, channel = stdChannel(), sagaMonitor, ...options } = {}) {
let boundRunSaga
// ...
function sagaMiddleware({ getState, dispatch }) {
boundRunSaga = runSaga.bind(null, {
...options,
context,
channel,
dispatch,
getState,
sagaMonitor,
})
return next => action => {
if (sagaMonitor && sagaMonitor.actionDispatched) {
sagaMonitor.actionDispatched(action)
}
// 执行reducer的逻辑
const result = next(action) // hit reducers
// 执行回调
channel.put(action)
return result
}
}
sagaMiddleware.run = (...args) => {
//...
return boundRunSaga(...args)
}
// ...
return sagaMiddleware
}
channel是redux-saga存储回调和触发回调的对象,通过执行stdChannel
获取,在stdChannel
方法内部会执行multicastChannel
。
- 执行
take
方法,将回调存储到currentTakers
数组中,并将matcher
赋值给回调函数的MATCH
属性 - 执行
put
方法,如果action与MATCH
函数相匹配,则触发相应的回调函数,
export function multicastChannel() {
let closed = false
let currentTakers = []
let nextTakers = currentTakers
//...
return {
put(input) {
//...
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) {
//...
cb[MATCH] = matcher
ensureCanMutateNextTakers()
nextTakers.push(cb)
cb.cancel = once(() => {
ensureCanMutateNextTakers()
remove(nextTakers, cb)
})
}
}
}
那什么时候执行channel.take
存储对应的回调函数呢?
执行中间件的run方法
在sagaMiddleware.run
方法中,会执行proc
函数,proc
函数会自动执行generator。
与co模块原理相同,都在内部封装了next
函数,自动执行generator。
// co模块的简单实现
function co (gen) {
const g = gen();
function next(val) {
const stage = g.next(val);
if (stage.done) {
return stage.value;
}
if (!!stage.value.then) {
stage.value.then(next);
} else {
stage.value(next);
}
};
next()
};
而proc
函数的next方法,会调用digestEffect
函数,去执行传入effect
对象。
function next(arg, isErr) {
let result
// ...
if (!result.done) {
digestEffect(result.value, parentEffectId, next)
}
}
不同的effect对象会根据type值从effectRunnerMap中取出相应的执行函数。例如fork类型的effect会执行runForkEffect
,take类型的effect回去执行runTakeEffect
。
function runEffect(effect, effectId, currCb) {
//...
const effectRunner = effectRunnerMap[effect.type]
effectRunner(env, effect.payload, currCb, executingContext)
}