redux 源码小结

251 阅读5分钟

一、 Redux 怎么使用?举个🌰

// 1. 设置默认数据
const initState = {
  name: '',
  age: -1,
};

// 2. 创建一个reducer
function reducer(state = initState, action) {
  switch (action.type) {
    case 'SET_NAME':
      return { ...state, name: action.data };
    case 'SET_AGE':
      return { ...istate, age: action.data };
    default:
      return state;
  }
}

// 3. 创建store
const store = createStore(reducer);

// 4. 分发action修改state
store.dispatch({ type: 'SET_AGE', data: 22 });

// 5. 订阅 store,传递一个函数,只要 store 数据改变,这个函数就会被执行
store.subscribe(() => {
  console.log(store.getState());
});

二、文件结构

image-20210511172631713.png

三、源码分析

  1. index.js

这个文件是整个Redux的入口文件,主要暴露了一些redux API

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'

// 经过压缩打包后函数的名称应该变成一个简单的字母,可以判断代码是否已经压缩
function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      'This means that you are running a slower development build of Redux. ' +
      'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
      'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
      'to ensure you have the correct code for your production build.'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

  1. 最核心的createStore.js
import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

export default function createStore(reducer, preloadedState, enhancer) {
  // preloadedState必须是一个对象,enhancer必须是一个function
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function.'
    )
  }

  // 如果preloadedState为function且enhancer为undefined, 说明没有传初始state,但是存在一个enhancer,此时把preloadedState变为enhancer
  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.')
    }
		
    // 如果传入的参数符合enhancer的执行条件,就会执行enhancer(增强函数),其实就是执行中间件,其需要与applyMiddleWare函数配合。这里先不讨论,后续说到applyMiddleWare函数时再详细说明,我们现在只知道它做了一些处理然后重新调用了createStore并且enhancer参数为undefined
    return enhancer(createStore)(reducer, preloadedState)
  }

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

  let currentReducer = reducer   // 当前reducer
  let currentState = preloadedState    // 当前的State
  let currentListeners = []    // 当前订阅的监听函数队列
  let nextListeners = currentListeners   // 浅拷贝当前的监听函数队列
  let isDispatching = false  // 标识是否正在进行dispatch

 
  
 // 对 currentListeners 做了浅拷贝以便于可以在 dispatch 的时候使用 nextListeners 作为临时的 listener。这样可以防止使用者在 dispatch 的时候调用 subscribe/unsubscribe 出现 bug。避免相互影响
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // store.getState() 获取store 当前的state
  function getState() {
    // 如果当前正在进行dispatch,不可以读取state, 这样做是为了确保获取到的state是最新的
    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
  }

  // store.substribe 用来设置监听函数,当调用dispatch改变state后,会执行所有的监听函数
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }

    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#subscribelistener for more details.'
      )
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    // 返回一个取消当前订阅监听的函数
    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      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#subscribelistener for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
      currentListeners = null
    }
  }

  // dispatch 函数,通过传入一个action来修改state,这也是外部修改state的唯一方式(ps: 当然也可以通过store.getState()获取到state的引用,然后直接修改也是可以的(不会自动调用监听函数),但强烈建议不要这样做,)
  function dispatch(action) {
    // action 必须要是一个普通对象,既action.__proto__ == Object.protptype
    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?'
      )
    }
		
    // 一次只能执行一个dispatch
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      // 执行reducer返回一个新的state,然后重新赋值给currentState,这就是为什么dispatch能改变state的原因
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    // dispatch过后执行所有的监听函数
    // 将nextListeners赋值给currentListeners和listeners
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  // replaceReducer 函数用于计算替换当前store中的reducer
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
   // 重新发送一个dispatch初始化state
    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之后,state并没有被初始化,所以发送一个init dispatch返回我们reducer中定义的初始化state
  dispatch({ type: ActionTypes.INIT })

  // 暴露出去的store api
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}

  1. combineReducers.js

顾名思义,这个函数是用来合并多个reducer作为一个reducer的,当我们业务很复杂时,通常想分出多个reducer,每个业务场景给一个单独的reducer,但是createStore又只能传入一个reducer,这个时候就需要combinereducers

import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'

function getUndefinedStateErrorMessage(key, action) {
  const actionType = action && action.type
  const actionDescription =
    (actionType && `action "${String(actionType)}"`) || 'an action'

  return (
    `Given ${actionDescription}, reducer "${key}" returned undefined. ` +
    `To ignore an action, you must explicitly return the previous state. ` +
    `If you want this reducer to hold no value, you can return null instead of undefined.`
  )
}

function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  const reducerKeys = Object.keys(reducers)
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState argument passed to createStore'
      : 'previous state received by the reducer'

  if (reducerKeys.length === 0) {
    return (
      'Store does not have a valid reducer. Make sure the argument passed ' +
      'to combineReducers is an object whose values are reducers.'
    )
  }

  if (!isPlainObject(inputState)) {
    return (
      `The ${argumentName} has unexpected type of "` +
      {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
      `". Expected argument to be an object with the following ` +
      `keys: "${reducerKeys.join('", "')}"`
    )
  }

  const unexpectedKeys = Object.keys(inputState).filter(
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })

  if (action && action.type === ActionTypes.REPLACE) return

  if (unexpectedKeys.length > 0) {
    return (
      `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
      `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
      `Expected to find one of the known reducer keys instead: ` +
      `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
    )
  }
}

function assertReducerShape(reducers) {
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
          `If the state passed to the reducer is undefined, you must ` +
          `explicitly return the initial state. The initial state may ` +
          `not be undefined. If you don't want to set a value for this reducer, ` +
          `you can use null instead of undefined.`
      )
    }

    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
          `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
          `namespace. They are considered private. Instead, you must return the ` +
          `current state for any unknown actions, unless it is undefined, ` +
          `in which case you must return the initial state, regardless of the ` +
          `action type. The initial state may not be undefined, but can be null.`
      )
    }
  })
}


export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  // 将传入的 reducers 做一个浅拷贝,并且剔除不是 Function 的 reducer。finalReducers为最终有效的reducers
  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}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

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

    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)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

  1. compose

    这是五个 API 里唯一一个能单独拿出来用的函数,就是函数式编程里常用的组合函数,和 redux 本身没有什么多大关系。先了解下函数式编程的一些概念:

纯函数是这样一种函数,即相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用

其实compose函数想要做的事就是把 const res = fn1(fn2(fn3(fn4(x)))) 这个嵌套的调用方式改为 const res = compose(fn1, fn2, fn3, fn4)(x)

举个🌰

import { compose } from 'redux'

const x = 1;
const fn1 = x => x + 1;
const fn2 = x => x + 2;
const fn3 = x => x + 3;
const fn4 = x => x + 4;

// 如果这里我想求得这样的值
const a = fn1(fn2(fn3(fn4(x))));   // 1 + 4 + 3 + 2 + 1 = 11

// 可以直接使用compose

const b = compose(fn1, fn2, fn3, fn3)(1)  // b同样等于11,与上面是一样的效果

compose函数的实现主要使用了Array.prototype.reduce

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

// redux实现compose的方式比较优雅,可能你难以理解,我们可以写个容易理解的版本
function compose(...funcs) {
  return (...args) => {
    let res;
    for (let i = funcs.length - 1; i >= 0; i--) {
      res = funcs[i](...args);
    }
    return res;
  }
}

  1. applyMiddenWare

applyMiddleware用来添加中间件,在修改数据的时候redux通过改造dispatch来实现中间件。 applyMiddleWare接收多个middleware作为参数,redux中间件有规定的函数签名,即:

const middleware = ({ dispatch, getState }) => next => action => next(action)

image-20210809183342984.png

import compose from './compose'

export default function applyMiddleware(...middlewares) {
  // 当我们调用applyMiddleware来使用中间件时,会把之前的creteStore函数传到这里,args就是reducer和preloadedState
  return createStore => (...args) => {
    // 创建最初的store
    const store = createStore(...args)
    // 定义一个dispatch函数,调用它会throw error,在middleware创建时,不能调用
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    // 定义一个middlewareAPI对象,这个对象中有getState和dispatch两个属性,将做为中间件的参数
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    // 将用户传入的中间件依次执行,并将middrewareAPI作为参数传给各个middleware
    // 此时chain的值为各个middleware返回值组成的数组
    // [next =>  action => { do something return next(action) }, next =>  action => { do something return next(action) }]
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 更新dispatch, 通过compose形成一个调用链,返回一个加强版dispatch,next指代下一个函数的注册, 这就是中间件的返回值要是next(action)的原因,如果执行到了最后,next就是原始的store.dispatch方法
    dispatch = compose(...chain)(store.dispatch)
    // 返回一个加强版的store
    return {
      ...store,
      dispatch
    }
  }
}

看一下redux-thunk源码(Redux不支持异步dispatch,thunk中间件用来解决此问题):

// 使用它thunk中间件之后,在业务代码中我们可以dispatch一个函数
function getData(dispatch) {
   const data = await new Promise(resolve => {
    setTimeout(() => {
      resolve([1])
    }, 1000)
   });
   dispatch({ type: 'SET_LIST', payload: data });
}
useEffect(() => {
  dispatch(getData)
}, [])


// 源码
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    // 如果dispatch的是一个函数(可以是异步的),执行这个函数,且传入dispatch和getState参数,
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

我们来实现一个超级简单的logger中间件, 在dispatch前后打印日志

export default function logger() {
  return ({ getState }) => next => action => {
    console.log('will dispatch', action, getState())
    const res = next(action)
    console.log('did dispatch', getState())
    return res
  }
}

怎么使用

import thunkMiddleware from 'redux-thunk'
import { createStore, applyMiddleware } from 'redux'

const store = createStore(reducer, applyMiddleware(thunkMiddleware, logger))