1.为什么createStore中既存在currentListeners也存在nextListeners?

看过源码的同学应该了解,createStore函数为了保存store的订阅者,不仅保存了当前的订阅者currentListeners而且也保存了nextListeners。createStore中有一个内部函数ensureCanMutateNextListeners:

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
}

这个函数实质的作用是确保可以改变nextListeners,如果nextListeners与currentListeners一致的话,将currentListeners做一个拷贝赋值给nextListeners,然后所有的新增订阅取消订阅操作都会集中在nextListeners,比如我们看订阅的函数subscribe:

function subscribe(listener) {
// ......
    let isSubscribed = true

    ensureCanMutateNextListeners()            //确保可以改变nextListeners
    nextListeners.push(listener)                //新增订阅,将listener添加到nextListeners数组的后面

    return function unsubscribe() {            //返回的是一个取消订阅的函数unsubscribe
        // ......
        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)    //取消订阅,删除对应位置的listener
}

我们发现订阅和解除订阅都是在nextListeners做的操作,然后每次dispatch一个action都会做如下的操作:

function dispatch(action) {
  try {
    isDispatching = true
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }
  // 相当于currentListeners = nextListeners const listeners = currentListeners
  const listeners = currentListeners = nextListeners            //更新listeners数组
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()            //每次dispatch都会去执行listener
  }
  return action
}

我们发现在dispatch中做了const listeners = currentListeners = nextListeners,相当于更新了当前currentListeners为nextListeners,然后通知订阅者,到这里我们不禁要问为什么要存在这个nextListeners?

先看一下代码中的解释:

/**
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 the dispatch() that is currently in progress.
However, the next dispatch() call, whether nested or not, 
will use a more recent snapshot of the subscription list. 
*/

解释起来就是:订阅者(subscriptions)在每次dispatch( )调用之前都是一份快照(snapshotted)。如果在listener被调用期间,进行订阅或者取消订阅,在本次的dispatch( )过程中是不会生效的,然而在下一次的dispatch( )调用中,无论dispatch是否嵌套调用,都使用最近一次的快照订阅者列表

用一张图来表示:
image.png

增加nextListener这个副本是为了避免在遍历listeners的过程中由于subscribe或者unsubscribe对listeners进行的修改而引起的某个listener被漏掉了。比如你在遍历到某个listener的时候,在这个listener中unsubscribe了一个在当前listener之前的listener,这个时候继续i ++的时候就会直接跳过当前listener的下一个listener,导致当前的listener没有被执行。

2.为什么Reducer中不能进行dispatch操作?

我们知道在reducer函数中是不能执行dispatch操作的。一方面,reducer作为计算下一次state的纯函数是不应该承担执行dispatch这样的操作。另一方面,即使你尝试在reducer中执行dispatch,也不会成功,并且会得到"Reducers may not dispatch actions."的提示。因为在dispatch函数就做了相关的限制:

function dispatch(action) {
    if (isDispatching) {            //正在执行reducer的时候如果执行dispatch会抛出错误
      throw new Error('Reducers may not dispatch actions.')
    }
    try {
      isDispatching = true            //执行reducer之前就会把isDispatching置为true
      currentState = currentReducer(currentState, action)            //执行reducer
    } finally {
      isDispatching = false
    }

    //...notice listener
}

执行dispatch时就会将标志位isDispatching置为true。然后如果在currentReducer(currentState, action)执行的过程中又执行了dispatch,那么就会抛出错误('Reducers may not dispatch actions.')。之所以做如此的限制,是因为在dispatch中会引起reducer的执行,如果此时reducer中又执行了dispatch,这样就落入了一个死循环,所以就要避免reducer中执行dispatch。

3.问什么applyMiddleware中的middlewareAPI中的dispatch要用闭包包裹?

首先来看下中间件函数的结构:

export default function createMiddleware({ getState }) {
    return (next) => 
        (action) => {
            //before
            //......
            next(action)
            //after
            //......
        };
}

中间件函数内部代码执行次序分别是:
image.png

以左图为例,如果在中间件函数中调用了dispatch( ),执行的次序变成了:
image.png

所以给中间件函数传入的middlewareAPI中的dispatch函数是经过applyMiddleware改造过的dispatch,而不是redux原生的dispatch。所以我们通过一个闭包包裹dispatch:

(action) => dispatch(action)

这里传递给middlewareAPI的dispatch是一个匿名函数,匿名函数通过闭包去访问变量dispatch。后面有:

dispatch = compose(...chain, store.dispatch);

这里把变量dispatch的值改变了,以后通过闭包访问到的变量肯定是增强的函数。

这样我们在后面给dispatch赋值为dispatch = compose(...chain, store.dispatch);,这样只要 dispatch 更新了,middlewareAPI 中的 dispatch 应用也会发生变化。如果我们写成:

var middlewareAPI = {
    getState: store.getState,
    dispatch: dispatch
};

那中间件函数中接受到的dispatch永远只能是最开始的redux中的dispatch。


JacksonHuang
74 声望3 粉丝