redux源码解读

264 阅读7分钟

Redux源码解读

早就想找个时间看看react全家桶的源码了 这次先从redux看起,后续还会有react-redux和react-router-redux

下面看代码 目录结构如下:

  1. applyMiddleware.js
  2. bindActionCreator.js
  3. combineReducers.js
  4. compose.js
  5. createStore.js
  6. index.js

先看入口文件

index.js

入口文件主要是暴露1-5的核心方法 代码就不贴了

createStore.js

// reducer是一个函数, 接收一个单独的reducer或者经过combineRedeucers组合的多个reducer
// preloadedState 是指需要提前加载的state 会和各store的state一起整合到state树里
// enhancer 是store的增强器 下面会重点讲  会涉及到compose和applyMiddleware这两个核心函数
export default function createStore(reducer, preloadedState, enhancer) {
  // 如果第二个参数是个函数 并且没有第三个参数 就默认没有穿preloadedState 直接传了增强器函数
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 如果有增强器但是增强器不是函数 抛出异常
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    // 存在函数类型的增强器就返回执行增强器执行后的结果
    // 这里可以推测出增强器的函数结构应该是这样的
    // const enhancer = createStore => (reducer, preloadedState, enhancer) => {
    //   const store = createStore(reducer, preloadedState, enhancer)
    //   return {
    //     ...store,
    //   }
    // }
    // 所以可以猜测enhancer是为了通过覆盖达到增强store的某一个属性  下面再细说
    return enhancer(createStore)(reducer, preloadedState)
  }

  // 如果reducer不是函数 抛异常
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  // 当前的state树
  let currentState = preloadedState
  // 这里为什么药声明两个监听队列呢?
  // 作者是为了在dispatch的时候能够完整的执行所有的事件监听 
  // 有人试验过 在一个监听里面取消另一个监听 这个一个无效操作  feature or bug???
  let currentListeners = []
  let nextListeners = currentListeners
  // 是否正在dispatch过程
  let isDispatching = false

  // 确保nextListeners 和 currentListeners没有引用关系
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 这个是获取到最新state内容
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }
  
  // 这个subscribe是为了给currentListener增加监听事件 下面的dispatch函数中 在执行完dispatch操作后 会遍历这个监听队列依次订阅的监听事件
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
    
    // 保证不能再dispatch的过程中触发监听事件
    if (isDispatching) {
      throw new Error(
        'You may not call store.subscribe() while the reducer is executing. ' +
          'If you would like to be notified after the store has been updated, subscribe from a ' +
          'component and invoke store.getState() in the callback to access the latest state. ' +
          'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
      )
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    // 监听事件入栈
    nextListeners.push(listener)
    
    // 这种写法还是比较常见且妙的 添加监听事件后 通过闭包返回一个移除当前监听事件的方法
    // 用法如下
    // const store = createStore(...)
    // const subscribe = store.subscribe(() => {})
    // 移除只需要执行subscribe()就行了
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }
      // 无法再dispatch过程中移除监听事件
      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      // 监听事件移除
      nextListeners.splice(index, 1)
    }
  }

  // 每个redux middleware都是一个对dispatch的一个增强
  // 是redux的dispatch方法 也是页面修改树状态的唯一途径
  function dispatch(action) {
    // 首先判断action是不是一个纯对象 即通过{}或者new Object()创建
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }
    // 保证每个action必须有type属性
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    // 一个action结束才能调用下一个
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 这是执行reducer的函数 下面讲到combineReducers的时候再重点讲
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // 遍历触发subscribe添加的监听
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // 替换当前reducer 这个只在热更新上用过
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }
  
  function observable() {
    const 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' || observer === null) {
          throw new TypeError('Expected the observer to be an object.')
        }

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

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

      [?observable]() {
        return this
      }
    }
  }
  
  // 初始化store
  // 收集每个独立store上的state  因为每个store都default返回自身的state
  dispatch({ type: ActionTypes.INIT })

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

下面我们看看combineReducers做了什么

combineReducers.js

// 看这个文件名字就知道是一个组合多个reducer的方法
// reducers的结构如下
/*
* {
*   demo1: function(state, action) {
*     switch(action.type) {}
*   },
*   demo2: function(state, action) {
*     switch(action.type) {}
*   },
* }
* */
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }
    // 保证每个reducer对应的value都是函数
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    // 这个方法是为了保证每个自定义reducer至少有一个初始的state和兜底返回值
    assertReducerShape(finalReducers)
  } catch (e) {s
    shapeAssertionError = e
  }

  // 这一步才是重点
  // 返回createStore/dispatch方法中有一行: currentState = currentReducer(currentState, action)
  // 所以这个方法才是真正修改状态树的方法
  // combination方法会遍历传进来的多个reducers组成的对象 依次执行每个reducer 得到每个reducer相应的state
  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    // 每次dispatch的都会遍历reducer树让每个小reducer执行这个action
    // 保证每次dispatch 都是根据上次的state进行操作的
    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]
      const nextStateForKey = reducer(previousStateForKey, action)
      // 该type没有定义 抛异常
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

看完以上代码我们先举个小例子介绍一下enhancer到底是何方神圣

  1. touch index.js
  2. 开始写demo
const { createStore, combineReducers, applyMiddleware, compose } = require('redux')
const initialState = {
  count: 1,
}
function count1(state = initialState, action) {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        count: state.count + 1,
      }
    default:
      return state
  }
}
const store = createStore(count1)
const action1 = { type: 'ADD' }
store.dispatch(action1)
// 我们打印store.getStore()
// 结果是 { count1: { count: 2 } }
/*
* 上面是redux的基本使用 我们主要讲的是store增强器
* 以上也有提到增强器的主要结构
* 我们再做个小demo
* */

// 这是一个每次dispatch前后打印日志的增强器
const loggerEnhancer = createStore => (reducer, proloadedState) => {
  const store = createStore(reducer, proloadedState)
  // 重写dispatch方法 或者说是在原有的dispatch上包了一层
  function dispatch(action) {
    console.log(`dispatch action type: ${action.type}`)
    const _action = store.dispatch(action)
    const nextState = store.getState()
    console.log('new state is:', nextState)
    // 原来的dispatch就是个纯函数  所以这里的_action就是入参action
    return _action
  }
  return {
    ...store,
    dispatch,
  }
}

const storeWithLogger = createStore(count1, loggerEnhancer)
storeWithLogger.dispatch(action1)
// 这样在dispatch的时候就会出现我们的logger内容
 
// 写到这儿的时候我在想 咦 这玩意儿怎么和redux中间件这么像 肯定compose和applyMiddleware有关系
// 好奇心驱使下 去看了compose和applyMiddleware

接着我们看看compose和applyMiddleware做了什么吧~

compose.js

export default 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)))
}

compose的代码很少 通过reduce方法将各中间件形成一个执行链 前一个中间件套在后一个的外层 并接受后一个函数的执行结果作为自己的入参 举个栗子吧

const a = function () {}
const b = function () {}
const c = function () {}
compose([a, b, c])
// 先处理a和b 返回 
const ab = function (...args) {
  return a(b(...args))
}
// 再处理ab和c 返回 
const abc = function (...args) {
  return ab(c(...args))
  // 这个c(...args)执行结果就是ab的入参
  // 所以我们可以换个写法
  return a(b(c(...args)))
}
// 换而言之compose的返回值就是 (...args) => a(b(c(...args))) 

applyMiddleware.js

我们看看applyMiddleware是怎么实现的 找找他是怎么通过compose组合成enhancer的

// 看到这段代码 我觉得猜测是对的 是不是有点像上面的loggerEnhancer的结构 重写dispatch方法
// 所以得出结论 与其说增强器是对store的增强不如说是对dispatch的增强
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // 这里的...args是 reducers、preloadedState、enhancer
    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))
    // 这里的compose的作用是包裹所有dispatch操作
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

我们再写个使用applyMiddleware小demo 通过demo我们再进行设想

const { createStore, applyMiddleware, compose } = require('redux')
const initialState = {
  count: 1,
}
function count1(state = initialState, action) {
  switch (action.type) {
    case 'ADD':
      return {
        ...state,
        count: state.count + 1,
      }
    default:
      return state
  }
}
const store = createStore(count1, applyMiddleware(fn1, fn2))

// 上面两个fn1和fn2我们都没定义 现在我们根据applyMiddleware猜测一下fn1和fn2是啥样子的
// 根据loggerEnhancer的设想 
const enhancer = createStore => (reducers, preloadedState, enhancer) => {} 
// applyMiddleware也应该是这样的函数体

const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 这行代码可以看出 fn1的雏形 应该是这样的
const fn1 = ({ dispatch, getState }) => {}
// 但是通过compose方法还能接受store.dispatch作为入参 说明fn1应该是这样的
const fn1 = ({ dispatch, getState }) => dispatch => {}
// 通过返回的是一个dispatch 我可以知道fn1原来是这样的
const fn1 = ({ dispatch, getState }) => dispatch => action => {}
// 举个栗子
const a = dispatch => action => {}
const b = dispatch => action => {}
// compose([a, b])的结果为 (...args) => a(b(...args))
// compose([a, b])(store.dispatch) 结果为 a(b(dispatch)) 再做简化就是a(action => {})
// 从这里可以看出 a执行的dispatch其实就是 b(dispatch)的结果 
// 如果还有c c执行dispatch就是a(dispatch) 从而达到中间件的作用
// a执行的dispatch就是dispatch


// 举个更确切的栗子
const fn1 = store => next => {
  console.log('next1')
  return action => {
    console.log('fn1')
    next(action)
    console.log('fn1')
  }
}

const fn2 = store => next => {
  console.log('next2')
  return action => {
    console.log('fn2')
    next(action)
    console.log('fn2')
  }
}
/*
* 在执行applyMiddleware时  执行了compose(...chain)(store.sidpatch) 执行顺序是 next2 、 next1  这是中间件入栈的顺序
* 但是调用的顺序不一样  有兴趣可以了解一下洋葱模型
* 执行store.dispatch(action1) 实际打印顺序是 fn1、fn2、fn2、fn1
* fn2在传入store.dispatch的时候 会将
*/
const dispatchFromStore = action => {
  console.log('fn2')
  next(action)
  console.log('fn2')
}
/*
* 当做返回值传给fn1的next
* 那fn1的执行过程就是
* */
const dispatchFromFn2 = action => {
  console.log('fn1')
  dispatchFromStore(action)
  console.log('fn1')
}
// 所以打印顺序是fn1、fn2、fn2、fn1就不奇怪了