redux源码分析

286 阅读5分钟

我觉得redux对于初学者并不是很友好,很多概念都不太好理解,会使用redux之后有必要看源码

createStore

redux的store存储了应用的状态树,要改变state只能通过dispatch()方法。

虽然文档中明确指出store只能有一个,但是我在工作的项目中store有可能不止一个,比如多页面应用。

参数

createStore有3个参数,分别是reducerpreloadedStateenhancer

  1. reducer。这里传入的reducer通常是通过combineReducers集成的reducer
  2. preloadedState。初始化store时候很有必要传入一个初始化的state,一来可以给应用页面一个初始值,二来可以让自己或者别人了解整个app的state结构。
  3. enhancer。通常就是一些redux的中间件,中间件的概念有点绕。

createStore做了参数校验和类型检测,除了reducer是必须传入之外,其余的两个参数都不是必须的。 传入的形式包含以下几种。

  • createStore(reducer)
  • createStore(reducer, enhancer)
  • createStore(reducer, preloadedState)
  • createStore(reducer, preloadedState, enhaner)

内部实现

createStore的内部包含了3个重要的变量和4个供外部调用的方法。

变量

  1. currentState。存储了整个store管理的状态树。
  2. currentListeners。redux其实是观察者模式的一个实践。我们可以把currentListeners看做state变化后待执行的函数列表。
  3. isDispatching。是否正在进行dispatch
  4. nextListners。当进行subscribe操作时候,先把新的listener函数push到nextListners数组中,作为一个最新listener数组快照。

方法

  1. getState 外部访问store内部state的唯一方法。方法直接把currentState内部变量直接返回。
function getState() {
  return currentState;
}
  1. subscribe 用于增加一个listener到nextListners。每次进行dispatch方法时候,nextListenrs替换currentListeners,传入的listener会被执行。通常来说我们会把UI的渲染,作为listener传入到subscribe中,每当state变化,redux会通知UI进行render。 listener不会看到每一个所有state的变化,因为state可能会在dispatch中变化多次后,listener才被执行。 subscribe方法返回一个函数闭包,作为取消订阅。
function subscribe() {
  let isSubscribed = true
  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }
    isSubscribed = false
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}
  1. dispatch 外部唯一能改变state的方法。调用reducer获得最新的state(reducer就是构建state树的函数)并执行每一个listener方法。 dispatch只能传递plain objectaction(普通的JavaScript对象),如果要传递一个thunk,需要使用中间件,redux-thunk。
function dispatch(action) {
    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 把nextListeners替换currentListeners
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }
  1. replaceReducer 更换reducer方法。很少时候会使用这个方法。

combineReducers

把多个reducer合到一个函数中。combineReducers会调用每一个reducer,并把每一个reducer返回的state合并到state树中。

参数

类型为一个object。每个键对应的必须为一个reducer函数。

const params = {
  key1: reducerfunc1,
  key2: reducerfunc2,
};

假设reducerfunc1和reducerfunc2返回的state形式分别为

const state1 = {
  v1: '',
  v2: 0,
};

const state2 = [];

那么我们的state树就是这样形式,通过getState获取到的state对象如下。

const state = {
  key1: {
    v1: '',
    v2: 0,
  },
  key2: [],
}

内部实现

闭包之前的代码都是reducer检测

  • 过滤那些不是function的reducer(reducers的每一个key必须对应的reducer方法)
  • 查看每一个reducer方法是否有initState,default type是否返回state

代码很好理解。

export default function combineReducers(reducers) {
  return function combination(state = {}, action) {
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // reducer调用前的state
      const previousStateForKey = state[key]
      // reducer调用后的state
      const nextStateForKey = reducer(previousStateForKey, action)
      // 更新state树快照
      nextState[key] = nextStateForKey
      // state是否有变动的flag。若reducer执行导致state变动,返回一个全新的state对象,所以可以直接比较对象来判断是否有改变。
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

applyMiddleware

中间件,我觉得是整个redux中比较有意思,并且稍微有点难理解的部分。

可以在官网查看中间件的文档。 可以把中间件理解为在dispatch方法的前后做一些操作。也可以类比为java的切面。

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    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: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

其实这里可以理解为俄罗斯套娃,原始的store.dispatch就是套娃的最里面那个,所有的中间件按照数组的顺序,一个把一个套住。而这个套娃的过程由compose完成。

要理解这部分,我们把中间件redux-thunk的源码也拿过来看看。 redux-thunk让我们的action为函数。注意!! 原本的action只能是一个plain object。 以下是redux-thunk中间件的写法,必须传入store,dispatch,action后才会执行真正的函数实体。 这里用的是柯里化,只有参数够了,才会去执行函数体。

// 稍微改动过的redux-thunk
const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
};

export default thunk;

我这里一开始看不懂,为什么一开始dispatch赋值为一个空函数? 其实compose完成之后会返回一个新的dispatch,这个dispatch会替换掉那个空函数

const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

最后返回store变量。

return {
  ...store,
  dispatch
}

参考

  1. redux middleware
  2. redux 源码