如果我们替换 store 实例的 dispatch function 会怎么样?因为 Redux store 是原生的 object 带有少量方法,而且我们写的是 JavaScript,因此我们可以用 monkeypatch dispatch 的方式实现:
1 2 3 4 5 6 7
let next = store.dispatch store.dispatch = functiondispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result }
functionpatchStoreToAddLogging(store) { let next = store.dispatch store.dispatch = functiondispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } }
Monkeypatching 是 hack 的行为。“根据喜好替换任何方法”,这是什么类型的 API?让我们从本质上修改。前面我们的 function 替换了 store.dispatch。如果他返回一个新的 dispatch function 代替会怎么样?
1 2 3 4 5 6 7 8 9 10 11 12 13
functionlogger(store) { let next = store.dispatch
// Previously: // store.dispatch = function dispatchAndLog(action) {
returnfunctiondispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } }
functionlogger(store) { // Must point to the function returned by the previous middleware: let next = store.dispatch
returnfunctiondispatchAndLog(action) { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result } }
const logger = store => next => action => { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result }
const logger = store => next => action => { console.log('dispatching', action) let result = next(action) console.log('next state', store.getState()) return result }
let todoApp = combineReducers(reducers) let store = createStore( todoApp, // applyMiddleware() tells createStore() how to handle middleware applyMiddleware(logger, crashReporter) )
就是这样!现在任何 dispatch 的 action 到 store 实例都会经过 logger 和 crashReporter:
1 2
// Will flow through both logger and crashReporter middleware! store.dispatch(addTodo('Use Redux'))
redux-thunk 的总结
redux-thunk
Any return value from the inner function will be available as the return value of dispatch itself.
The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
/** * Logs all actions and states after they are dispatched. */ const logger = store => next => action => { console.group(action.type) console.info('dispatching', action) let result = next(action) console.log('next state', store.getState()) console.groupEnd(action.type) return result }
/** * Sends crash reports as state is updated and listeners are notified. */ const crashReporter = store => next => action => { try { return next(action) } catch (err) { console.error('Caught an exception!', err) Raven.captureException(err, { extra: { action, state: store.getState() } }) throw err } }
/** * Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds. * Makes `dispatch` return a function to cancel the timeout in this case. */ const timeoutScheduler = store => next => action => { if (!action.meta || !action.meta.delay) { return next(action) }
let timeoutId = setTimeout( () => next(action), action.meta.delay )
/** * Schedules actions with { meta: { raf: true } } to be dispatched inside a rAF loop * frame. Makes `dispatch` return a function to remove the action from the queue in * this case. */ const rafScheduler = store => next => { let queuedActions = [] let frame = null
/** * Lets you dispatch promises in addition to actions. * If the promise is resolved, its result will be dispatched as an action. * The promise is returned from `dispatch` so the caller may handle rejection. */ const vanillaPromise = store => next => action => { if (typeof action.then !== 'function') { return next(action) }
/** * Lets you dispatch special actions with a { promise } field. * * This middleware will turn them into a single action at the beginning, * and a single success (or failure) action when the `promise` resolves. * * For convenience, `dispatch` will return the promise so the caller can wait. */ const readyStatePromise = store => next => action => { if (!action.promise) { return next(action) }
next(makeAction(false)) return action.promise.then( result => next(makeAction(true, { result })), error => next(makeAction(true, { error })) ) }
/** * Lets you dispatch a function instead of an action. * This function will receive `dispatch` and `getState` as arguments. * * Useful for early exits (conditions over `getState()`), as well * as for async control flow (it can `dispatch()` something else). * * `dispatch` will return the return value of the dispatched function. */ const thunk = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action)
// You can use all of them! (It doesn’t mean you should.) let todoApp = combineReducers(reducers) let store = createStore( todoApp, applyMiddleware( rafScheduler, timeoutScheduler, thunk, vanillaPromise, readyStatePromise, logger, crashReporter ) )