作者:涂鸦-李青
来涂鸦工作: job.tuya.com/
isDispatching
Redux源码里的createStore方法维护了一个isDispatching变量,表示dispatch的状态,当调用dispatch时会将该变量赋值为true,详细代码为
let isDispatching = false
function dispatch(action: A) {
// ...
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// ...
return action
}
那Redux需要isDispatching做什么,还在哪里使用了它?在源码中搜索这个变量,发现除了dispatch函数 本身使用了isDispatching,还在getState、subscribe、unsubscribe方法中发现同样消费了isDispatching,其逻辑相似,即isDispatching为true时抛出错误。代码为
// subscribe unsubscribe
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
// ...
return function unsubscribe() {
// ...
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api/store#subscribelistener for more details.'
)
}
// ...
}
}
// getState
function getState(): S {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState as S
}
看完这两部分代码会让人疑惑,isDispatching好像没什么作用,我们知道js是单线程语言,并且dispatch、getState、subscribe、unsubscribe都是同步函数,既然是同步场景,我们在调用dispatch时,js会执行完这个函数再处理其他函数,不存在dispatch和getState、subscribe、unsubscribe同时执行的情况。但问题就出现在dispatch这个函数本身,我们再仔细观察dispatch核心实现。
function dispatch(action: A) {
// ...
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
}
dispatch将action交给reducer处理,这里的currentReducer就是createStore传入的reducer,即开发者编写的reducer。假如有这样一个reducer:
const reducer = (state={}, action) => {
store.dispatch({type: 'SOMEACTION'})
return state
}
这个reducer内部又调用dispatch方法,若没有isDispatching, dispatch执行时会发生dispatch -> reducer -> dispatch -> reducer无限循环调用导致堆栈溢出。因此Redux认为外界传入的reducer是不安全的,便通过isDispatching来限制reducer。
currentListeners和nextListeners
Redux实现了订阅-监听-发布功能,通过subscribe方法订阅添加一个监听者listener,每一次调用dispatch时Redux会遍历通知所有的监听器。这个功能正常代码实现方式是:
// 简易代码
const listeners = []
function subscribe(listener) {
listeners.push(listener)
return function unsubscribe() {
const index = listeners.indexOf(listener)
listeners.splice(index, 1)
}
}
function dispatch() {
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
}
但Redux内部维护了currentListeners和nextListeners两个监听器数据源,其代码为
let currentListeners = []
let nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function subscribe(listener) {
// ...
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
// ...
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
// ...
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
实际上subscribe方法接收的是外部传进来的监听器,若监听器中又执行了subscribe或unsubscribe方法,则会造成监听器执行的混乱甚至出错。比如这样订阅:
function loopSubscribe () {
store.subscribe(loopSubscribe)
}
loopSubscribe()
store.dispatch()
此时只维护一个监听器变量的代码会造成死循环,为了防止传入的listener又调用subscribe添加监听器,Redux使用了两个变量来维护监听器,凡是涉及到操作listener的方法即subscribe和unsubscribe都是操作nextListeners数据, dispatch时将nextListeners合并到currentListeners,遍历通知所有的currentListeners。换句话说,dispatch会进行一次监听器快照,这时如果监听器listener又执行了subscribe或unsubscribe方法,只会在下一次dispatch方法执行时生效。
applyMiddleware
Redux生态离不开中间件,其中redux-thunk就是众多中间件的一个,其代码为:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
redux-thunk看起来相对复杂,依次返回了三层函数,我们来看Redux中间件核心函数applyMiddleware如何处理中间件,
function applyMiddleware(
...middlewares
) {
return createStore => {
const store = createStore(reducer, preloadedState)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
Redux会对中间件的三层函数依次解开,首先第一层Redux遍历所有的中间件并赋予了中间件middlewareAPI的能力,middlewareAPI中有一个dispatch方法,但此时的dispatch并非原来store中的dispatch,它随后会被修改。
此时中间件被交给compose进行链式组合,其结果就是给每个中间件增加了调用下一个中间件的能力(next),这是第二层。
compose的实现方式也很巧妙:
function compose(...funcs) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
假如有两个中间件a, b,则compose结果为**(...args) => a(b(...args))**,compose返回结果又传入了store.dispatch,即args为store.dispatch,这样中间件a可以调用next执行中间件b,而中间件b调用next是store.dispatch。
最后一层就是开发者调用dispatch将action传入,即第三层函数能够访问action对象。
compose后的dispatch为增强版的dispatch,注意,最后一个中间件调用next是在调用store.dispatch,即未增强的dispatch。
结束
Redux目前虽然有点「过时」,但其中的设计和细节仍然有借鉴之处,如isDispatching锁、监听器快照等实现帮助我们产出更加健壮的代码。
来涂鸦工作: job.tuya.com/