Redux 源码分析

首先让我们来看下 combineReducers 函数

  1. // 传入一个 object
  2. export default function combineReducers(reducers) {
  3. // 获取该 Object 的 key 值
  4. const reducerKeys = Object.keys(reducers)
  5. // 过滤后的 reducers
  6. const finalReducers = {}
  7. // 获取每一个 key 对应的 value
  8. // 在开发环境下判断值是否为 undefined
  9. // 然后将值类型是函数的值放入 finalReducers
  10. for (let i = 0; i < reducerKeys.length; i++) {
  11. const key = reducerKeys[i]
  12. if (process.env.NODE_ENV !== 'production') {
  13. if (typeof reducers[key] === 'undefined') {
  14. warning(`No reducer provided for key "${key}"`)
  15. }
  16. }
  17. if (typeof reducers[key] === 'function') {
  18. finalReducers[key] = reducers[key]
  19. }
  20. }
  21. // 拿到过滤后的 reducers 的 key 值
  22. const finalReducerKeys = Object.keys(finalReducers)
  23. // 在开发环境下判断,保存不期望 key 的缓存用以下面做警告
  24. let unexpectedKeyCache
  25. if (process.env.NODE_ENV !== 'production') {
  26. unexpectedKeyCache = {}
  27. }
  28. let shapeAssertionError
  29. try {
  30. // 该函数解析在下面
  31. assertReducerShape(finalReducers)
  32. } catch (e) {
  33. shapeAssertionError = e
  34. }
  35. // combineReducers 函数返回一个函数,也就是合并后的 reducer 函数
  36. // 该函数返回总的 state
  37. // 并且你也可以发现这里使用了闭包,函数里面使用到了外面的一些属性
  38. return function combination(state = {}, action) {
  39. if (shapeAssertionError) {
  40. throw shapeAssertionError
  41. }
  42. // 该函数解析在下面
  43. if (process.env.NODE_ENV !== 'production') {
  44. const warningMessage = getUnexpectedStateShapeWarningMessage(
  45. state,
  46. finalReducers,
  47. action,
  48. unexpectedKeyCache
  49. )
  50. if (warningMessage) {
  51. warning(warningMessage)
  52. }
  53. }
  54. // state 是否改变
  55. let hasChanged = false
  56. // 改变后的 state
  57. const nextState = {}
  58. for (let i = 0; i < finalReducerKeys.length; i++) {
  59. // 拿到相应的 key
  60. const key = finalReducerKeys[i]
  61. // 获得 key 对应的 reducer 函数
  62. const reducer = finalReducers[key]
  63. // state 树下的 key 是与 finalReducers 下的 key 相同的
  64. // 所以你在 combineReducers 中传入的参数的 key 即代表了 各个 reducer 也代表了各个 state
  65. const previousStateForKey = state[key]
  66. // 然后执行 reducer 函数获得该 key 值对应的 state
  67. const nextStateForKey = reducer(previousStateForKey, action)
  68. // 判断 state 的值,undefined 的话就报错
  69. if (typeof nextStateForKey === 'undefined') {
  70. const errorMessage = getUndefinedStateErrorMessage(key, action)
  71. throw new Error(errorMessage)
  72. }
  73. // 然后将 value 塞进去
  74. nextState[key] = nextStateForKey
  75. // 如果 state 改变
  76. hasChanged = hasChanged || nextStateForKey !== previousStateForKey
  77. }
  78. // state 只要改变过,就返回新的 state
  79. return hasChanged ? nextState : state
  80. }
  81. }

combineReducers 函数总的来说很简单,总结来说就是接收一个对象,将参数过滤后返回一个函数。该函数里有一个过滤参数后的对象 finalReducers,遍历该对象,然后执行对象中的每一个 reducer 函数,最后将新的 state 返回。

接下来让我们来看看 combinrReducers 中用到的两个函数

  1. // 这是执行的第一个用于抛错的函数
  2. function assertReducerShape(reducers) {
  3. // 将 combineReducers 中的参数遍历
  4. Object.keys(reducers).forEach(key => {
  5. const reducer = reducers[key]
  6. // 给他传入一个 action
  7. const initialState = reducer(undefined, { type: ActionTypes.INIT })
  8. // 如果得到的 state 为 undefined 就抛错
  9. if (typeof initialState === 'undefined') {
  10. throw new Error(
  11. `Reducer "${key}" returned undefined during initialization. ` +
  12. `If the state passed to the reducer is undefined, you must ` +
  13. `explicitly return the initial state. The initial state may ` +
  14. `not be undefined. If you don't want to set a value for this reducer, ` +
  15. `you can use null instead of undefined.`
  16. )
  17. }
  18. // 再过滤一次,考虑到万一你在 reducer 中给 ActionTypes.INIT 返回了值
  19. // 传入一个随机的 action 判断值是否为 undefined
  20. const type =
  21. '@@redux/PROBE_UNKNOWN_ACTION_' +
  22. Math.random()
  23. .toString(36)
  24. .substring(7)
  25. .split('')
  26. .join('.')
  27. if (typeof reducer(undefined, { type }) === 'undefined') {
  28. throw new Error(
  29. `Reducer "${key}" returned undefined when probed with a random type. ` +
  30. `Don't try to handle ${
  31. ActionTypes.INIT
  32. } or other actions in "redux/*" ` +
  33. `namespace. They are considered private. Instead, you must return the ` +
  34. `current state for any unknown actions, unless it is undefined, ` +
  35. `in which case you must return the initial state, regardless of the ` +
  36. `action type. The initial state may not be undefined, but can be null.`
  37. )
  38. }
  39. })
  40. }
  41. function getUnexpectedStateShapeWarningMessage(
  42. inputState,
  43. reducers,
  44. action,
  45. unexpectedKeyCache
  46. ) {
  47. // 这里的 reducers 已经是 finalReducers
  48. const reducerKeys = Object.keys(reducers)
  49. const argumentName =
  50. action && action.type === ActionTypes.INIT
  51. ? 'preloadedState argument passed to createStore'
  52. : 'previous state received by the reducer'
  53. // 如果 finalReducers 为空
  54. if (reducerKeys.length === 0) {
  55. return (
  56. 'Store does not have a valid reducer. Make sure the argument passed ' +
  57. 'to combineReducers is an object whose values are reducers.'
  58. )
  59. }
  60. // 如果你传入的 state 不是对象
  61. if (!isPlainObject(inputState)) {
  62. return (
  63. `The ${argumentName} has unexpected type of "` +
  64. {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
  65. `". Expected argument to be an object with the following ` +
  66. `keys: "${reducerKeys.join('", "')}"`
  67. )
  68. }
  69. // 将参入的 state 于 finalReducers 下的 key 做比较,过滤出多余的 key
  70. const unexpectedKeys = Object.keys(inputState).filter(
  71. key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  72. )
  73. unexpectedKeys.forEach(key => {
  74. unexpectedKeyCache[key] = true
  75. })
  76. if (action && action.type === ActionTypes.REPLACE) return
  77. // 如果 unexpectedKeys 有值的话
  78. if (unexpectedKeys.length > 0) {
  79. return (
  80. `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
  81. `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
  82. `Expected to find one of the known reducer keys instead: ` +
  83. `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
  84. )
  85. }
  86. }

接下来让我们先来看看 compose 函数

  1. // 这个函数设计的很巧妙,通过传入函数引用的方式让我们完成多个函数的嵌套使用,术语叫做高阶函数
  2. // 通过使用 reduce 函数做到从右至左调用函数
  3. // 对于上面项目中的例子
  4. compose(
  5. applyMiddleware(thunkMiddleware),
  6. window.devToolsExtension ? window.devToolsExtension() : f => f
  7. )
  8. // 经过 compose 函数变成了 applyMiddleware(thunkMiddleware)(window.devToolsExtension()())
  9. // 所以在找不到 window.devToolsExtension 时你应该返回一个函数
  10. export default function compose(...funcs) {
  11. if (funcs.length === 0) {
  12. return arg => arg
  13. }
  14. if (funcs.length === 1) {
  15. return funcs[0]
  16. }
  17. return funcs.reduce((a, b) => (...args) => a(b(...args)))
  18. }

然后我们来解析 createStore 函数的部分代码

  1. export default function createStore(reducer, preloadedState, enhancer) {
  2. // 一般 preloadedState 用的少,判断类型,如果第二个参数是函数且没有第三个参数,就调换位置
  3. if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  4. enhancer = preloadedState
  5. preloadedState = undefined
  6. }
  7. // 判断 enhancer 是否是函数
  8. if (typeof enhancer !== 'undefined') {
  9. if (typeof enhancer !== 'function') {
  10. throw new Error('Expected the enhancer to be a function.')
  11. }
  12. // 类型没错的话,先执行 enhancer,然后再执行 createStore 函数
  13. return enhancer(createStore)(reducer, preloadedState)
  14. }
  15. // 判断 reducer 是否是函数
  16. if (typeof reducer !== 'function') {
  17. throw new Error('Expected the reducer to be a function.')
  18. }
  19. // 当前 reducer
  20. let currentReducer = reducer
  21. // 当前状态
  22. let currentState = preloadedState
  23. // 当前监听函数数组
  24. let currentListeners = []
  25. // 这是一个很重要的设计,为的就是每次在遍历监听器的时候保证 currentListeners 数组不变
  26. // 可以考虑下只存在 currentListeners 的情况,如果我在某个 subscribe 中再次执行 subscribe
  27. // 或者 unsubscribe,这样会导致当前的 currentListeners 数组大小发生改变,从而可能导致
  28. // 索引出错
  29. let nextListeners = currentListeners
  30. // reducer 是否正在执行
  31. let isDispatching = false
  32. // 如果 currentListeners 和 nextListeners 相同,就赋值回去
  33. function ensureCanMutateNextListeners() {
  34. if (nextListeners === currentListeners) {
  35. nextListeners = currentListeners.slice()
  36. }
  37. }
  38. // ......
  39. }

接下来先来介绍 applyMiddleware 函数

在这之前我需要先来介绍一下函数柯里化,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

  1. function add(a,b) { return a + b }
  2. add(1, 2) => 3
  3. // 对于以上函数如果使用柯里化可以这样改造
  4. function add(a) {
  5. return b => {
  6. return a + b
  7. }
  8. }
  9. add(1)(2) => 3
  10. // 你可以这样理解函数柯里化,通过闭包保存了外部的一个变量,然后返回一个接收参数的函数,在该函数中使用了保存的变量,然后再返回值。
  1. // 这个函数应该是整个源码中最难理解的一块了
  2. // 该函数返回一个柯里化的函数
  3. // 所以调用这个函数应该这样写 applyMiddleware(...middlewares)(createStore)(...args)
  4. export default function applyMiddleware(...middlewares) {
  5. return createStore => (...args) => {
  6. // 这里执行 createStore 函数,把 applyMiddleware 函数最后次调用的参数传进来
  7. const store = createStore(...args)
  8. let dispatch = () => {
  9. throw new Error(
  10. `Dispatching while constructing your middleware is not allowed. ` +
  11. `Other middleware would not be applied to this dispatch.`
  12. )
  13. }
  14. let chain = []
  15. // 每个中间件都应该有这两个函数
  16. const middlewareAPI = {
  17. getState: store.getState,
  18. dispatch: (...args) => dispatch(...args)
  19. }
  20. // 把 middlewares 中的每个中间件都传入 middlewareAPI
  21. chain = middlewares.map(middleware => middleware(middlewareAPI))
  22. // 和之前一样,从右至左调用每个中间件,然后传入 store.dispatch
  23. dispatch = compose(...chain)(store.dispatch)
  24. // 这里只看这部分代码有点抽象,我这里放入 redux-thunk 的代码来结合分析
  25. // createThunkMiddleware返回了3层函数,第一层函数接收 middlewareAPI 参数
  26. // 第二次函数接收 store.dispatch
  27. // 第三层函数接收 dispatch 中的参数
  28. {function createThunkMiddleware(extraArgument) {
  29. return ({ dispatch, getState }) => next => action => {
  30. // 判断 dispatch 中的参数是否为函数
  31. if (typeof action === 'function') {
  32. // 是函数的话再把这些参数传进去,直到 action 不为函数,执行 dispatch({tyep: 'XXX'})
  33. return action(dispatch, getState, extraArgument);
  34. }
  35. return next(action);
  36. };
  37. }
  38. const thunk = createThunkMiddleware();
  39. export default thunk;}
  40. // 最后把经过中间件加强后的 dispatch 于剩余 store 中的属性返回,这样你的 dispatch
  41. return {
  42. ...store,
  43. dispatch
  44. }
  45. }
  46. }

好了,我们现在将困难的部分都攻克了,来看一些简单的代码

  1. // 这个没啥好说的,就是把当前的 state 返回,但是当正在执行 reducer 时不能执行该方法
  2. function getState() {
  3. if (isDispatching) {
  4. throw new Error(
  5. 'You may not call store.getState() while the reducer is executing. ' +
  6. 'The reducer has already received the state as an argument. ' +
  7. 'Pass it down from the top reducer instead of reading it from the store.'
  8. )
  9. }
  10. return currentState
  11. }
  12. // 接收一个函数参数
  13. function subscribe(listener) {
  14. if (typeof listener !== 'function') {
  15. throw new Error('Expected listener to be a function.')
  16. }
  17. // 这部分最主要的设计 nextListeners 已经讲过,其他基本没什么好说的
  18. if (isDispatching) {
  19. throw new Error(
  20. 'You may not call store.subscribe() while the reducer is executing. ' +
  21. 'If you would like to be notified after the store has been updated, subscribe from a ' +
  22. 'component and invoke store.getState() in the callback to access the latest state. ' +
  23. 'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
  24. )
  25. }
  26. let isSubscribed = true
  27. ensureCanMutateNextListeners()
  28. nextListeners.push(listener)
  29. // 返回一个取消订阅函数
  30. return function unsubscribe() {
  31. if (!isSubscribed) {
  32. return
  33. }
  34. if (isDispatching) {
  35. throw new Error(
  36. 'You may not unsubscribe from a store listener while the reducer is executing. ' +
  37. 'See http://redux.js.org/docs/api/Store.html#subscribe for more details.'
  38. )
  39. }
  40. isSubscribed = false
  41. ensureCanMutateNextListeners()
  42. const index = nextListeners.indexOf(listener)
  43. nextListeners.splice(index, 1)
  44. }
  45. }
  46. function dispatch(action) {
  47. // 原生的 dispatch 会判断 action 是否为对象
  48. if (!isPlainObject(action)) {
  49. throw new Error(
  50. 'Actions must be plain objects. ' +
  51. 'Use custom middleware for async actions.'
  52. )
  53. }
  54. if (typeof action.type === 'undefined') {
  55. throw new Error(
  56. 'Actions may not have an undefined "type" property. ' +
  57. 'Have you misspelled a constant?'
  58. )
  59. }
  60. // 注意在 Reducers 中是不能执行 dispatch 函数的
  61. // 因为你一旦在 reducer 函数中执行 dispatch,会引发死循环
  62. if (isDispatching) {
  63. throw new Error('Reducers may not dispatch actions.')
  64. }
  65. // 执行 combineReducers 组合后的函数
  66. try {
  67. isDispatching = true
  68. currentState = currentReducer(currentState, action)
  69. } finally {
  70. isDispatching = false
  71. }
  72. // 然后遍历 currentListeners,执行数组中保存的函数
  73. const listeners = (currentListeners = nextListeners)
  74. for (let i = 0; i < listeners.length; i++) {
  75. const listener = listeners[i]
  76. listener()
  77. }
  78. return action
  79. }
  80. // 然后在 createStore 末尾会发起一个 action dispatch({ type: ActionTypes.INIT });
  81. // 用以初始化 state