前段时间看了Redux的源码,写了一篇关于Redux的源码分析: Redux:百行代码千行文档,没有看的小伙伴能够看一下,整篇文章主要是对Redux运行的原理进行了大体的解析,可是其实有不少内容并无明确地深究为何要这么作?本篇文章的内容主要就是我本身提出一些问题,而后试着去回答这个问题,再次作个广告,欢迎你们关注个人掘金帐号和个人博客。
javascript
看过源码的同窗应该了解,createStore
函数为了保存store
的订阅者,不只保存了当前的订阅者currentListeners
并且也保存了nextListeners
。createStore
中有一个内部函数ensureCanMutateNextListeners
:
java
function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
这个函数实质的做用是确保能够改变nextListeners
,若是nextListeners
与currentListeners
一致的话,将currentListeners
作一个拷贝赋值给nextListeners
,而后全部的操做都会集中在nextListeners
,好比咱们看订阅的函数subscribe
:git
function subscribe(listener) { // ...... let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { // ...... ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) }
咱们发现订阅和解除订阅都是在nextListeners
作的操做,而后每次dispatch
一个action
都会作以下的操做:github
function dispatch(action) { try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } // 至关于currentListeners = nextListeners const listeners = currentListeners const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
咱们发如今dispatch
中作了const listeners = currentListeners = nextListeners
,至关于更新了当前currentListeners
为nextListeners
,而后通知订阅者,到这里咱们不由要问为何要存在这个nextListeners
?
其实代码中的注释也是作了相关的解释:redux
The subscriptions are snapshotted just before every
dispatch()
call.If you subscribe or unsubscribe while the listeners are being invoked, this will not have any effect on thedispatch()
that is currently in progress.However, the nextdispatch()
call, whether nested or not, will use a more recent snapshot of the subscription list. 多线程
来让我这个六级没过的渣渣翻译一下: 订阅者(subscriptions)在每次dispatch()
调用以前都是一份快照(snapshotted)。若是你在listener
被调用期间,进行订阅或者退订,在本次的dispatch()
过程当中是不会生效的,然而在下一次的dispatch()
调用中,不管dispatch
是不是嵌套调用的,都将使用最近一次的快照订阅者列表。用图表示的效果以下:
咱们从这个图中能够看见,若是不存在这个nextListeners
这份快照的话,由于dispatch
致使的store
的改变,从而进一步通知订阅者,若是在通知订阅者的过程当中发生了其余的订阅(subscribe)和退订(unsubscribe),那确定会发生错误或者不肯定性。例如:好比在通知订阅的过程当中,若是发生了退订,那就既有可能成功退订(在通知以前就执行了nextListeners.splice(index, 1)
)或者没有成功退订(在已经通知了以后才执行了nextListeners.splice(index, 1)
),这固然是不行的。由于nextListeners
的存在因此通知订阅者的行为是明确的,订阅和退订是不会影响到本次订阅者通知的过程。闭包
这都没有问题,但是存在一个问题,JavaScript不是单线程的吗?怎么会出现上述所说的场景呢?百思不得其解的状况下,去Redux项目下开了一个issue,获得了维护者的回答:app
得了,咱们再来看看测试相关的代码吧。看完以后我了解到了。的确,由于JavaScript是单线程语言,不可能出现出现想上述所说的多线程场景,可是我忽略了一点,执行订阅者函数时,在这个回调函数中能够执行退订或者订阅事件。例如:函数
const store = createStore(reducers.todos) const unsubscribe1 = store.subscribe(() => { const unsubscribe2 = store.subscribe(()=>{}) })
这不就实现了在通知listener的过程当中混入订阅subscribe
与退订unsubscribe
吗?
源码分析
咱们知道在reducer
函数中是不能执行dispatch
操做的。一方面,reducer
做为计算下一次state
的纯函数是不该该承担执行dispatch
这样的操做。另外一方面,即便你尝试着在reducer
中执行dispatch
,也并不会成功,而且会获得"Reducers may not dispatch actions."的提示。由于在dispatch
函数就作了相关的限制:
function dispatch(action) { if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } //...notice listener }
在执行dispatch
时就会将标志位isDispatching
置为true
。而后若是在currentReducer(currentState, action)
执行的过程当中由执行了dispatch
,那么就会抛出错误('Reducers may not dispatch actions.')。之因此作如此的限制,是由于在dispatch
中会引发reducer
的执行,若是此时reducer
中又执行了dispatch
,这样就落入了一个死循环,因此就要避免reducer
中执行dispatch
。
关于Redux的中间件以前我写过一篇相关的文章Redux:Middleware你咋就这么难,没有看过的同窗能够了解一下,其实文章中也有一个地方没有明确的解释,当时初学不是很理解,如今来解释一下:
export default function applyMiddleware(...middlewares) { return (next) => (reducer, initialState) => { var store = next(reducer, initialState); var dispatch = store.dispatch; var chain = []; var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; chain = middlewares.map(middleware => middleware(middlewareAPI)); dispatch = compose(...chain, store.dispatch); return { ...store, dispatch }; }; }
这个问题的就是为何middlewareAPI中的dispathc要用闭包包裹,而不是直接传入呢?首先用一幅图来解释一下中间件:
如上图所示,中间件的执行过程很是相似于洋葱圈(Onion Rings),假设咱们在函数applyMiddleware
中传入中间件的顺序分别是mid一、mid二、mid3。而中间件函数的结构相似于:
export default function createMiddleware({ getState }) { return (next) => (action) => { //before //...... next(action) //after //...... }; }
那么中间件函数内部代码执行次序分别是:
可是若是在中间件函数中调用了dispatch
(用mid3-before
中为例),执行的次序就变成了:
因此给中间件函数传入的middlewareAPI
中dispatch
函数是通过applyMiddleware
改造过的dispatch
,而不是redux
原生的store.dispatch
。因此咱们经过一个闭包包裹dispatch
:
(action) => dispatch(action)
这样咱们在后面给dispatch
赋值为dispatch = compose(...chain, store.dispatch);
,这样只要 dispatch 更新了,middlewareAPI 中的 dispatch 应用也会发生变化。若是咱们写成:
var middlewareAPI = { getState: store.getState, dispatch: dispatch };
那中间件函数中接受到的dispatch
永远只能是最开始的redux
中的dispatch
。
最后,若是你们在阅读Redux源码时还有别的疑惑和感觉,欢迎你们在评论区相互交流,讨论和学习。