redux源码分析

前言

Redux是React生态中一个极其重要的状态管理库,在官网的介绍中,他能使得应用在不同的运行环境下表现出一致性,并使得应用容易被测试。另外通过time traveling debugger能带来更好的开发体验。(但是写起来比起mobx还是略显啰嗦)

redux中的三个概念

action

action是一个plain object(即action的prototype是object),如果用TypeScript的接口来定义action,它是这样子的

interface Action<T = any> {
  type: T;
}
interface AnyAction extends Action {
  // Allows any extra properties to be defined in an action.
  [extraProps: string]: any;
}

Action至少有一个type属性,这个type属性显然是用来表示这个action的类型的。然后可以包含其它属性,下面是一个action的例子

const ADD_TODO = 'ADD_TODO'
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

那么就有一类函数叫做action creator,它的类型定义是是这样的。

interface ActionCreator<A> {
  (...args: any[]): A;
}

这类函数接受若干个参数,然后返回一个action object。一个action creator的例子如下

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

这个action是由reducer接收的。

reducer

reducer也是一类函数,它接受当前的state和收到的action作为参数,返回一个新的state,reducer被要求是纯函数(即无副作用的函数),reducer的TypeScript定义如下

type Reducer<S = any, A extends Action = AnyAction> = (state: S | undefined, action: A) => S

在reducer中,通过判断action.type来得知收到的是何种action,以决定如何处理 一个reducer的例子如下

unction todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    default:
      return state
  }
}

注意,当reducer第一次运行,state为undefined,action为 { type: '@@redux/INIT' },这个时候要返回初始的initialState,另外,当前reducer不需要因当前action改变state时,也需要返回当前的state。 想象一下,当我们需要处理的action数量变得无限复杂时,把所有类型的action放在一个reducer里处理显然是不合适的。redux有一个combineReducer工具方法,把多个reducer合并成一个

import { combineReducers } from 'redux'
import {
  ADD_TODO,
  TOGGLE_TODO,
  SET_VISIBILITY_FILTER,
  VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
​
function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}const todoApp = combineReducers({
  visibilityFilter,
  todos
})export default todoApp

todoApp是一个reducer,他返回的state类型是这样的。

interface TodoApp {
  visibilityFilter: 'SHOW_ALL' | 'SHOW_COMPLETED', | 'SHOW_ACTIVE'
  todos: Array<{ text: string; completed: boolean; }>
}

每一次dispatch action的时候,todoApp这个reducer会将action传给每一个子reducer,并把新的state更新到对应的子字段中

store

store是一个对象,由createStore创建,他有这些功能

  1. 存储state
  2. 允许通过他的getState方法获取当前state
  3. 通过dispatch(action)来派发一个action获得新的state
  4. 通过subscribe(listener)方法注listener监听state的变化和action的接收
  5. 通过subscribe(listener)返回的方法取消监听。
interface Store<S = any, A extends Action = AnyAction> {
  dispatch: Dispatch<A>;
  getState(): S;
  subscribe(listener: () => void): Unsubscribe;
  replaceReducer(nextReducer: Reducer<S, A>): void;
}

创建一个store非常简单,将一个reducer传入createStore即可,他有个可选的参数,表示初始的state,在服务端渲染的应用中可以将获取的到的数据传入这个第二个参数。

combineReducer的实现

下面我会直接注释在代码里。

function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers)
  // 获得每个reducer的key
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  // 过滤掉reducer中非function的字段,避免调用了一个不是函数的玩意
  // 到这里finalReducers里的所有value肯定都是function
  var finalReducerKeys = Object.keys(finalReducers)
  var sanityError
  try {
    assertReducerSanity(finalReducers)
    // 这个函数判断reducer有没有initalState,如果有将会记录这个错误并在下面的代码中抛出异常
  } catch (e) {
    sanityError = e
  }

  // 最终返回的reducer
  return function combination(state = {}, action) {
    if (sanityError) {
      throw sanityError
    }

    // 非production模式下判断
    // 1. reducer是否为空对象({})
    // 2. action是否为plain object
    // 如果满足任意其1,抛出异常
    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    // 记录reducer返回的state是否改变
    var hasChanged = false
    var nextState = {}
    // 传入action,调用每个子reducer获得子state并更新到父state上
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      // 判断当前key的子state与之前的子state是否strict equal,如果否则说明父state改变了
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 返回当前state或者新的state
    // 这样做的原因是,使用redux的库(如react-redux)会通过strict equal(即 === 运算符)
    // 来判断state是否变化,因为参会不同的引用会导致外部认为state发生了变化,即使他们deepEqual
    return hasChanged ? nextState : state
  }
}

createStore实现

// createStore的三个参数,第一个是reducer,第二个是初始的state,第三个enhancer必须通过applyMiddleware来创建
function createStore(reducer, initialState, enhancer) {

  // 这种情况是仅仅传入了reducer和enhancer的情况,需要调整一下参数位置
  if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
    enhancer = initialState
    initialState = undefined
  }

  // enhancer存在时,通过enhancer去创建store
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, initialState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  var currentReducer = reducer
  var currentState = initialState
  var currentListeners = []
  var nextListeners = currentisteners
  var isDispatching = false

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

  /**
   * 获取state
   *
   * @returns {any} 返回当前state
   */
  function getState() {
    return currentState
  }

  /**
   * 添加一个listener,每当action被dispatch的时候,它将被调用。
   * Adds a change listener. It will be called any time an action is dispatched,
   * 有些state会隐性地被改变
   * 在回调用调用`getState()` 以获得当前的state
   * call `getState()` to read the current state tree inside the callback.
   * 
   * 你可能会在listener中dispatch action,请注意下列事项
   * 
   * 1. 所有的订阅都在每次调用`dispatch()`前留下快照,在listener中增加或取消订阅不会再当前的
   * dispatch流程中有效,只有下次dispatch才会有效
   *
   * 2. The listener should not expect to see all state changes, as the state
   * might have been updated multiple times during a nested `dispatch()` before
   * the listener is called. It is, however, guaranteed that all subscribers
   * registered before the `dispatch()` started will be called with the latest
   * state by the time it exits.
   *
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   */
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true
    // 判断当前的listeners是否与下一个listeners相等,相等说明listener没有被改变过,如果是从当前listeners拷贝出新的listenrs数组
    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    // 取消订阅的函数 
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   *
   * The `reducer` function, used to create the store, will be called with the
   * current state tree and the given `action`. Its return value will
   * be considered the **next** state of the tree, and the change listeners
   * will be notified.
   *
   * The base implementation only supports plain object actions. If you want to
   * dispatch a Promise, an Observable, a thunk, or something else, you need to
   * wrap your store creating function into the corresponding middleware. For
   * example, see the documentation for the `redux-thunk` package. Even the
   * middleware will eventually dispatch plain object actions using this method.
   *
   * @param {Object} action A plain object representing “what changed”. It is
   * a good idea to keep actions serializable so you can record and replay user
   * sessions, or use the time travelling `redux-devtools`. An action must have
   * a `type` property which may not be `undefined`. It is a good idea to use
   * string constants for action types.
   *
   * @returns {Object} For convenience, the same action object you dispatched.
   *
   * Note that, if you use a custom middleware, it may wrap `dispatch()` to
   * return something else (for example, a Promise you can await).
   */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }

    // 这种情况只可能reducer是异步的才会出现
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    // 将nextListeners与currentListeners合并
    var listeners = currentListeners = nextListeners
    // 依次调用每个listener
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }

  /**
   * 替换reducer,在某些情况下可能需要吧
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
    替换reducer后重新初始化store
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/zenparsing/es-observable
   */
  function observable() {
    var outerSubscribe = subscribe
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== 'object') {
          throw new TypeError('Expected the observer to be an object.')
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState())
          }
        }

        observeState()
        var unsubscribe = outerSubscribe(observeState)
        return { unsubscribe }
      },

      [$$observable]() {
        return this
      }
    }
  }

  // dispatch一个初始的action来获取reducer的initial state

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

applyMiddleware的实现


//
function compose(...funcs) 
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0, -1)
    return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
  }
}

function applyMiddleware(...middlewares) {
  // 返回上面createStore中的enhancer
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // chain中的每个函数执行后都会调用一个middleware
    // 每个middleware可能会修饰dispatch函数(比如打log等)
    // 被修饰过的dispatch函数会作为参数传给下一个middleware
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }

参考资料

redux官方文档

redux的index.d.ts定义文件

redux git repo