「React」问:redux工作原理

148 阅读3分钟

redux主要工作的是以下五个文件:

  • createStore.js
  • applyMiddleware.js
  • bindActionCreators.js 用于将传入的 actionCreator 与 dispatch 方法相结合,揉成一个新的方法
  • combineReducers.js 用于将多个  reducer 合并起来
  • compose.js 用于把接收到的函数从右向左进行组合

createStore

接收三个入参

  • reducer
  • 初始状态内容
  • 指定中间件

基本流程:

  • 处理没有传入初始状态的情况

  • 如果enhancer不为空,就用enhancer包裹createStore

    enhancer 就是异步中间件

  • 定义一些内部变量

  • 定义ensureCanMutateNextListeners方法

    该方法确保currentListeners与nextListeners不是同一个引用

  • 定义getState 该方法用于获取当前的状态

  • 定义subcribe 方法,该方法用于注册listeners ,也就是订阅监听函数

  • 定义dispacth 该方法用于派发action,调用reducer并触发订阅

  • 定义replaceReducer,该方法用于替换reducer

  • 定义observable方法 未使用到

  • 将getState、subcribe、dispacth放在store对象中返回

dispatch

function dispatch(action) {

  // 校验 action 的数据格式是否合法
  if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects. ' +'Use custom middleware for async actions.'
    )
  }

  // 约束 action 中必须有 type 属性作为 action 的唯一标识 
  if (typeof action.type === 'undefined') {
   throw new Error(
      'Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?'
    )
  }

  // 若当前已经位于 dispatch 的流程中,则不允许再度发起 dispatch(禁止套娃)
  if (isDispatching) {
    throw new Error('Reducers may not dispatch actions.')
  }

  try {
    // 执行 reducer 前,先"上锁",标记当前已经存在 dispatch 执行流程
    isDispatching = true
    // 调用 reducer,计算新的 state
    currentState = currentReducer(currentState, action)
  } finally {
    // 执行结束后,把"锁"打开,允许再次进行 dispatch
    isDispatching = false
  }

  // 触发订阅
  const listeners = (currentListeners = nextListeners);
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i];
    listener();
  }
  return action;
}

dispatch里面除了校验参数合法性之外,其实就做了两件事

  • 调用reducer ,计算新的state
  • 触发订阅,遍历listeners数组中的函数,并依次执行

Redux 中的“发布-订阅”模式:认识 subscribe

dispatch 中执行的 listeners 数组从订阅中来,而执行订阅需要调用 subscribe

subscribe 在订阅时只需要传入监听函数,而不需要传入事件类型。这是因为 Redux 中已经默认了订阅的对象就是“状态的变化(准确地说是 dispatch 函数的调用) ”这个事件。

工作流程:

  • 调用ensureCanMutateNextListeners,确保currentListeners与nextListeners不是同一个引用
  • 注册监听函数,将入参listener函数推入到nextListeners数组中
  • 返回取消订阅当前listener的方法

1、subscribe 是如何与 Redux 主流程结合的呢

在 store 对象创建成功后,通过调用 store.subscribe 来注册监听函数,也可以通过调用 subscribe 的返回函数来解绑监听函数,监听函数是用 listeners 数组来维护的;

dispatch action 发生时,Redux 会在 reducer 执行完毕后,将 listeners 数组中的监听函数逐个执行

2、为什么会有 currentListeners 和 nextListeners 两个 listeners 数组

要理解这个问题,我们首先要搞清楚 Redux 中的订阅过程和发布过程各自是如何处理 listeners 数组的。

订阅过程中:

createStore 的变量初始化阶段,nextListeners 会被赋值为 currentListeners,这之后两者确实指向同一个引用。

但在 subscribe 第一次被调用时,ensureCanMutateNextListeners会将 nextListeners 纠正为一个内容与 currentListeners 一致、但引用不同的新对象。

在 subscribe 的逻辑中,ensureCanMutateNextListeners 每次都会在 listener 注册前被无条件调用,用以确保两个数组引用不同。紧跟在 ensureCanMutateNextListeners 之后执行的是 listener 的注册逻辑,我们可以对应源码中看到 listener 最终会被注册到 nextListeners 数组中去:

发布过程中:

在触发订阅的过程中,currentListeners 会被赋值为 nextListeners,而实际被执行的 listeners 数组又会被赋值为 currentListeners。因此,最终被执行的 listeners 数组,实际上和当前的 nextListeners 指向同一个引用

currentListeners 数组用于确保监听函数执行过程的稳定性

假如说不存在 currentListeners,那么也就意味着不需要 ensureCanMutateNextListeners 这个动作。

若没有 ensureCanMutateNextListeners,那么一旦在循环的过程中,listeners数组的长度发生改变,那么可能就会抛异常