深入浅出ーRedux-sagaソース
12349 ワード
目次 reduxミドルウェア作成 redux-sagaのミドルウェアのソースコード解析 概念解析 ミドルウェア編集部:エントランスファイル-->multicast Chanel-->ブザー エントランスファイル mlticast Chanel関数 ブザー関数 起動中間件:runSaga-->proc ルンSaga proc Effect redux中間文書作成
ミドルウェアはどう書きますか?
redux-sagaの中间件のソースコードの解析
概念解析
redux-sagaソースは三つの部分に分けられます.中間部品部 中間部品 を起動する. effects全体として、redux-sagaは一連のactionを傍受し、その後、actionに対してどのような操作をするかは、ユーザから入ってきたgenerator関数によって決定され、いくつかの列API(take\put\call\race\forkなど)を提供し、操作によって生成されるeffectを操作する .
中間部品編纂部分:エントランスファイル-->multicartChanel-->ブザー
インポートファイル
*入り口ファイルは標準的な中間部品の編纂方式で、チャンネル.put(*)を入れました.
multicast ChanelはstdChanelが実行した後の結果で、actionを伝えます.
bufferは行列で、actionを保存します.
ルンサガ
ルンサガ:あなたが入ってきたサガ関数は、ゲナート関数です.
procは長い関数です.中には多くのサブ関数が含まれていますが、まとめて二つのことをしました.は、あなたが入ってきたgenerator関数 を実行します.は、task記述 を返す.
上の説明から、サガのミドルウェアは、二つのことをしました.あなたが入ってきたgenerator関数に基づいて、nextは を実行します.あなたのゲナート関数には、put、call、raceなどの副作用があります.これらの操作によって他の操作が行われます.これらの操作は一つのことしかできません.つまり、effectとは何ですか?
ミドルウェアはどう書きますか?
({dispatch, getState}) => next => action => {
// write your code here
next(action)
}
上の関数のパラメータはどういう意味ですか?{dispatch, getState}: dispatch getState redux
next:
action: action
具体的にはReduxソース解析を参照してください.redux-sagaの中间件のソースコードの解析
概念解析
redux-sagaソースは三つの部分に分けられます.
中間部品編纂部分:エントランスファイル-->multicartChanel-->ブザー
インポートファイル
*入り口ファイルは標準的な中間部品の編纂方式で、チャンネル.put(*)を入れました.
// redux-saga ,
// context、options ,
function sagaMiddlewareFactory({ context = {}, ...options } = {}) {
const { sagaMonitor, logger, onError, effectMiddlewares } = options
let boundRunSaga
//
function sagaMiddleware({ getState, dispatch }) {
const channel = stdChannel()
// identity identity = v => v
channel.put = (options.emitter || identity)(channel.put)
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
}
multicast Chanel関数multicast ChanelはstdChanelが実行した後の結果で、actionを伝えます.
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
}
oasp関数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)
}
}
あのputは何をしましたか?putはmulticartChanel関数が実行した結果、オブジェクトに戻り、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,
}
}
ブザー関数bufferは行列で、actionを保存します.
import { kTrue, noop } from './utils'
const BUFFER_OVERFLOW = "Channel's Buffer overflow!"
const ON_OVERFLOW_THROW = 1
const ON_OVERFLOW_DROP = 2
const ON_OVERFLOW_SLIDE = 3
const ON_OVERFLOW_EXPAND = 4
const zeroBuffer = { isEmpty: kTrue, put: noop, take: noop }
function ringBuffer(limit = 10, overflowAction) {
let arr = new Array(limit)
let length = 0
let pushIndex = 0
let popIndex = 0
const push = it => {
arr[pushIndex] = it
pushIndex = (pushIndex + 1) % limit
length++
}
const take = () => {
if (length != 0) {
let it = arr[popIndex]
arr[popIndex] = null
length--
popIndex = (popIndex + 1) % limit
return it
}
}
const flush = () => {
let items = []
while (length) {
items.push(take())
}
return items
}
return {
isEmpty: () => length == 0,
put: it => {
if (length < limit) {
push(it)
} else {
let doubledLimit
switch (overflowAction) {
case ON_OVERFLOW_THROW:
throw new Error(BUFFER_OVERFLOW)
case ON_OVERFLOW_SLIDE:
arr[pushIndex] = it
pushIndex = (pushIndex + 1) % limit
popIndex = pushIndex
break
case ON_OVERFLOW_EXPAND:
doubledLimit = 2 * limit
arr = flush()
length = arr.length
pushIndex = arr.length
popIndex = 0
arr.length = doubledLimit
limit = doubledLimit
push(it)
break
default:
// DROP
}
}
},
take,
flush,
}
}
export const none = () => zeroBuffer
export const fixed = limit => ringBuffer(limit, ON_OVERFLOW_THROW)
export const dropping = limit => ringBuffer(limit, ON_OVERFLOW_DROP)
export const sliding = limit => ringBuffer(limit, ON_OVERFLOW_SLIDE)
export const expanding = initialSize => ringBuffer(initialSize, ON_OVERFLOW_EXPAND)
起動中間件:runSaga-->procルンサガ
ルンサガ:あなたが入ってきたサガ関数は、ゲナート関数です.
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()
}
}
procprocは長い関数です.中には多くのサブ関数が含まれていますが、まとめて二つのことをしました.
function proc(env, iterator, parentContext, parentEffectId, meta, cont) {
const taskContext = Object.create(parentContext)
const finalRunEffect = env.finalizeRunEffect(runEffect)
const task = newTask(parentEffectId, meta, cont)
const mainTask = { meta, cancel: cancelMain, _isRunning: true, _isCancelled: false }
const taskQueue = forkQueue(
mainTask,
function onAbort() {
cancelledDueToErrorTasks.push(...taskQueue.getTaskNames())
},
end,
)
next()
return task
}
nextは自動実行関数です.function next(arg, isErr) {
if (!mainTask._isRunning) {
throw new Error('Trying to resume an already finished generator')
}
try {
let result
if (isErr) {
result = iterator.throw(arg)
} else if (shouldCancel(arg)) {
mainTask._isCancelled = true
next.cancel()
result = is.func(iterator.return) ? iterator.return(TASK_CANCEL) : { done: true, value: TASK_CANCEL }
} else if (shouldTerminate(arg)) {
result = is.func(iterator.return) ? iterator.return() : { done: true }
} else {
result = iterator.next(arg)
}
if (!result.done) {
digestEffect(result.value, parentEffectId, '', next)
} else {
mainTask._isRunning = false
mainTask.cont(result.value)
}
} catch (error) {
if (mainTask._isCancelled) {
env.logError(error)
}
mainTask._isRunning = false
mainTask.cont(error, true)
}
}
runEffect関数:異なるeffect関数によって、異なる動作を実行します.function runEffect(effect, effectId, currCb) {
if (is.promise(effect)) {
resolvePromise(effect, currCb)
} else if (is.iterator(effect)) {
resolveIterator(effect, effectId, meta, currCb)
} else if (effect && effect[IO]) {
const { type, payload } = effect
if (type === effectTypes.TAKE) runTakeEffect(payload, currCb)
else if (type === effectTypes.PUT) runPutEffect(payload, currCb)
else if (type === effectTypes.ALL) runAllEffect(payload, effectId, currCb)
else if (type === effectTypes.RACE) runRaceEffect(payload, effectId, currCb)
else if (type === effectTypes.CALL) runCallEffect(payload, effectId, currCb)
else if (type === effectTypes.CPS) runCPSEffect(payload, currCb)
else if (type === effectTypes.FORK) runForkEffect(payload, effectId, currCb)
else if (type === effectTypes.JOIN) runJoinEffect(payload, currCb)
else if (type === effectTypes.CANCEL) runCancelEffect(payload, currCb)
else if (type === effectTypes.SELECT) runSelectEffect(payload, currCb)
else if (type === effectTypes.ACTION_CHANNEL) runChannelEffect(payload, currCb)
else if (type === effectTypes.FLUSH) runFlushEffect(payload, currCb)
else if (type === effectTypes.CANCELLED) runCancelledEffect(payload, currCb)
else if (type === effectTypes.GET_CONTEXT) runGetContextEffect(payload, currCb)
else if (type === effectTypes.SET_CONTEXT) runSetContextEffect(payload, currCb)
else currCb(effect)
} else {
// anything else returned as is
currCb(effect)
}
}
Effect上の説明から、サガのミドルウェアは、二つのことをしました.
export function race(effects) {
return makeEffect(effectTypes.RACE, effects)
}
const makeEffect = (type, payload) => ({ [IO]: true, type, payload })
Effectは一つの対象ですが、これらのオブジェクトの解釈は上のrunEffectに任せてput-effectを例に説明してみます.function runPutEffect({ channel, action, resolve }, cb) {
asap(() => {
let result
try {
// dispatch action
result = (channel ? channel.put : env.dispatch)(action)
} catch (error) {
cb(error, true)
return
}
if (resolve && is.promise(result)) {
resolvePromise(result, cb)
} else {
cb(result)
}
})
}