超详细的Redux源码解析

657 阅读7分钟

前言

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。主要解决了组件间通信问题,redux通过对store的管理和控制,可以很方便的实现页面状态的管理和切片。

阅读本片文章你能学到createStore、compose、applyMiddleware、combineReduces的源码解析,包括洋葱模型,高阶组件,科里化等高级知识点,废话不多说,下面直接步入主题。

createStore

createStore是redux的核心,用于生成一个store实例。

// reducer必须是一个函数,preloadedState是一个对象,enhancer是一个函数
// enhancer是加强store.dispatch的函数
export function createStore(reducer, preloadedState, enhancer) { 
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  // 如果使用了中间件会返回一个包含被加强的dispatch的store实例。
  if (typeof enhancer !== 'undefined') {
    // 这一步主要是对dispatch的加强
    return enhancer(createStore)(reducer, preloadedState)
  }
  // 如果没有使用中间件会向下执行返回一个普通store实例
  let currentReducer = reducer
  // 当前state
  let currentState = preloadedState
  // 当前监听函数数组
  let currentListeners = []
  // 下一个监听函数数组
  let nextListeners = currentListeners
  // 判断是否在执行dispatch里面的reducer函数
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice() // 浅拷贝
    }
  }

  function getState() { // 典型的闭包
    if (isDispatching) {
      throw new Error('你不能在reducer执行的时候调用store.getState()')
    }
    return currentState
  }

  function subscribe(listener) { // 添加订阅的方法
    // 防止你在reducer函数里调用dispatch,dispatch一个action,action又会执行reducer,进入死循环
    if (isDispatching) {
      throw new Error('你不能在reducer执行的时候调用store.subscribe()')
    }
    let isSubscribed = true // 表示正在监听

    ensureCanMutateNextListeners()
    nextListeners.push(listener)
    // 发布订阅有2种取消订阅方式:
    // 一种是将函数push进去,如果有2个相同的就将当前的删除
    // 一种是返回一个取消订阅的函数,此处使用了第二种
    return function unsubscribe() {
      if (!isSubscribed) { // 未监听直接返回
        return
      }
      if (isDispatching) {
        throw new Error('你不能在reducer执行的时候取消订阅')
      }
      isSubscribed = false
      ensureCanMutateNextListeners()
      // 通过index找到然后删除
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }
  function dispatch(action) { // action必须是包含type的对象
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    try {
      isDispatching = true // 标识将要执行reducer函数
      // 执行reducer函数会返回一个新的state,赋值给currentState
      currentState = currentReducer(currentState, action)
    } finally {
      // 执行完reducer函数后,标识isDispatching为false
      isDispatching = false
    }
    // state变化之后,通知订阅者状态已经更新
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      // 循环执行每个订阅者(本质是函数)
      listener()
    }
    return action
  }

  function replaceReducer(nextReducer) { // nextReducer必须是函数,否则报错
    currentReducer = nextReducer
    //此操作的效果与ActionTypes.INIT类似。
    dispatch({ type: ActionTypes.REPLACE })
  }

  function observable() {
    const outerSubscribe = subscribe
    return {
      subscribe(observer) { // observer必须是对象
        function observeState() {
          if (observer.next) { // observer应该有next方法
            observer.next(getState())
          }
        }

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

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

  // 创建store时 会dispatch一个“INIT”的action,
  // reducer里面匹配不到对应的type就会返回默认值state
  dispatch({ type: ActionTypes.INIT }) 

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }
}
export const legacy_createStore = createStore

compose(...functions)

compose的作用是从右到左来组合多个函数。当需要把多个 store 增强器 依次执行的时候,需要用到它。 参数functions是需要合成的多个函数,预计每个函数接收一个参数,它的返回值将作为一个参数提供给它左边的函数,以此类推。compose(funA,funB,funC) => compose(funA(funB(funC())))

function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg
  }
  if (funcs.length === 1) { // 如果只有一个中间件,就返回第一个
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
  // 如果这个不好理解可以写成下面这样
  // return funcs.reduce((a,b) => {
  //  return (...args) => {
  //        return a(b(...args))
  //  }
  //})
}

const add = x => x + 2
const mutiply = y => y * 2
compose(add, multiply) // 会先执行multiply,得到的结果作为参数传到add函数执行

applyMiddleware

applyMiddleware是一个增强器,组合多个中间件,最终增强store.dispatch函数,dispatch时,可以串联执行所有中间件。applyMiddleware的函数签名是:(...middleware) => (createStore) => (...agrs) => {}

  • 我们通过createStore(reducers, [], applyMiddleware(中间件名称))时 会先执行applyMiddleware得到函数(createStore) => (...args) => {}
  • 之后会真正执行createStore函数,由上面的createStore源码可以看出,使用了中间件的时候createStore会 执行return enhancer(createStore)(reducer,preloadedState)
  • 执行enhancer(createStore)就是执行(createStore) => (...args) => {} 得到签名为(...args) => {}的函数。
  • 之后执行(...args) => {}函数将reducer和preloadedState作为参数传进去。
export default function applyMiddleware(...middlewares) {
  return (createStore) => (...args) => {
    // 执行到这的时候,我们指定...args其实就是调用enhancer(createStore)(reducer,preloadedState)
    // 传进来的参数reducer和preloadedState
    const store = createStore(...args) // 将参数传进去得到一个store实例对象,以便跟增强的dispatch一起返回
    let dispatch = () => {
      throw new Error('不允许在构建中间件时进行dispatch。其他中间件将不会应用于该dispatch')
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    }
    
    // 遍历中间件,将middlewareAPI传进去,得到的chain是一个数组
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    
    利用compose函数依次执行中间件函数得到加强后的dispatch
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }
}

注意上面middlewareAPI中的dispatch必须写成一个匿名函数,如果直接将store.dispatch返回,中间件中拿到的是原始的dispatch,而写成匿名函数利用闭包原理,拿到的是经过compose(...chain)(store.dispatch)加强后的dispatch。

洋葱模型

洋葱模型执行的过程就是从外面一层一层的进去再一层一层的出来。

下面我们定义三个中间件,依次执行

function M1 ({getState}) {
  return (next) => {
    return (action) => {
      console.log('M1 开始')
      // 调用middleware链中下一个middleware的dispatch
      next(action)
      console.log('M1 结束')
    }
  }
}
function M2 ({getState}) {
  return (next) => {
    return (action) => {
      console.log('M2 开始')
      // 调用middleware链中下一个middleware的dispatch
      next(action)
      console.log('M2 结束')
    }
  }
}
function M3 ({getState}) {
  return (next) => {
    return (action) => {
      console.log('M3 开始')
      // 调用middleware链中下一个middleware的dispatch
      next(action)
      console.log('M3 结束')
    }
  }
}

function reducers (state,action) {
  if(action.type === 'ADD') {
    console.log('======')
  }
  return {}
}
const store = createStore(reducers, {}, applyMiddleware(M1, M2, M3))
store.dispatch({type: 'ADD'})
  • 调用dispatch({type: 'ADD'})后会先执行M1里的(action)=>{},打印'M1 开始'
  • M1里遇到next(action)会执行下一个中间件M2
  • 执行M2的副作用函数(action)=>{},打印'M2 开始'
  • M2里调用next(action)会执行下一个中间件M3
  • 执行M3的副作用函数(action)=>{},打印'M3 开始'
  • 由于M3中间件的next是store.dispatch,就会执行dispatch方法,打印'======='
  • M3执行完next(action)之后,打印'M3 结束',M3副作用执行完毕回到M2
  • M2打印'M2 结束', M2副作用执行完毕回到M1
  • M1打印'M1 结束', 至此中间件都被执行完毕。

M1、M2、M3的next是如何绑定的呢?
先把M1、M2、M3中间件完整的函数签名列出来
M1:store=>next=>action=>{next(action)}
M2:store=>next=>action=>{next(action)}
M3:store=>next=>action=>{next(action)}

applyMiddleware里执行完middleware.map(mid => mid(middlewareAPI))后就将getState和dispatch绑定了,M1、M2、M3中间件函数签名变成下面这样,分别对应A1,B1,C1
M1的签名A1:next=>action=>{next(action)}
M2的签名B1:next=>action=>{next(action)}
M3的签名C1:next=>action=>{next(action)}

chain是一个数组[(next)=>(action)=>{}, (next)=>(action)=>{}, ...]

compose(...chain)执行完后得到函数Fun:()=> A1(B1(C1(...argument))) 也就是中间件M3执行的结果作为参数传入到M2,M2执行的结果作为参数传给M1

执行Fun传入(store.dispatch),是将store.dispatch作为next先传到了M3,而M2和M1的next分别是M3和M2执行的结果。
M1、M2、M3的函数签名变成下面这样分别对应A2,B2,C2 M1的签名A2:action=>{next(action)}
M2的签名B2:action=>{next(action)}
M3的签名C2:action=>{next(action)}
M3的next是store.dispatch,M2的next是M3的函数签名,M1的next是M2的函数签名

所以 dispatch = M1的签名:action=>{next(action)}

当页面调用store.dispatch时就在执行增强的dispatch,执行M1的函数签名,M1的next是M2的函数签名, M2函数签名中的next是M3的函数签名,最终M3接收action执行了reducer。

洋葱模型的好处:如果有多个中间件,有些中间件需要依赖其他中间件的结果,如果没有洋葱模型执行顺序可能会出乎我们的预期,而洋葱模型可以保证执行的顺序

combineReducers

combineReducers辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore方法。

每个传入combineReducers的 reducer 都需满足以下规则:

  • 所有未匹配到的 action,必须把它接收到的第一个参数也就是那个state原封不动返回。
  • 永远不能返回 undefined。遇到这种情况时 combineReducers 会抛异常。
  • 如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。
function combineReducers(reducers) { // reducers是对象
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {} // 最终汇总的reducer
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    // reducer是函数才收集到finalReducers
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)
  
  /****省略了一些校验****/
  
  return function combination(state = {}, action) {
  
    /****省略了一些校验**/
    
    // hasChanged用来标识状态是否改变,改变了返回新状态否则返回初始状态
    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      // 上一个状态的值
      const previousStateForKey = state[key]
      // 执行reducer得到新值
      const nextStateForKey = reducer(previousStateForKey, action)
      
      /****此处省略了nextStateForKey是undefined的异常处理***/
      
      // 如果新值不是undefined,保存到nextState
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

总结

以上就是redux源码解析的所有内容了。相信大家对redux的原理有了充分的了解,如果学过vuex的还可以点此查看Vuex源码解析,对比二者之间的区别。最后的最后,别忘了点赞哟!