写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址

Redux的核心思想是:将应用的状态state存储在唯一的store中。通过store.dispatch一个action来描述触发了什么动作,用reducer处理相应的action并返回新的state。需要注意的是:创建store的时候需要传入reducer,真正可以改变应用状态state的是store.dispatchAPI。

相关文件说明

  • applyMiddleware.js:middleware串联起来生成一个更强大的dispatch函数,这就是中间件的本质作用;
  • bindActionCreators.js:action creators转成拥有同名keys的对象,使用dispatch把每个action creator包围起来,使用时可以直接调用;
  • combineReducers.js: 将多个reducer组合起来,每一个reducer独立管理自己对应的state
  • compose.js:middleware从右向左依次调用,函数式编程中的常用方法,被applyMiddleware调用;
  • createStore.js: 最核心功能,创建一个store,包括实现了subscribe,unsubscribe,dispatch及state的储存;
  • index.js: 对外export
  • utils: 一些小的辅助函数供其他的函数调用 ├── actionTypes.js: redux内置的action,用来初始化initialState ├── isPlainObject.js: 用来判断是否为单纯对象 └── warning.js: 控制台输出一个警告提示

推荐源码的阅读顺序为:

index.js -> creatStore.js -> applyMiddleware.js (compose.js) -> combineReducers.js -> bindActionCreators.js
1

redux是一个状态管理框架,是在Flux的基础上产生的,基本思想是:保证数据的单向流动,同时便于控制、使用、测试。

index.js

index.js是整个redux的入口文件,尾部的export出来的方法是不是都很熟悉,每个方法对应了一个js文件。

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

// 如果使用minified(压缩过的)的redux代码会降低性能。
// 这里的isCrushed函数主要是为了验证在非生产环境下的redux代码是否压缩过。如果被压缩了,那么isCrushed.name !== 'isCrushed'为true
// 如果不是production环境且压缩了,给出warning
if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' && // 有的浏览器(IE)并不支持Function.name,必须判断先判断是否支持Function.name,才能判断是否minified
  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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

该代码有两个功能:

  1. 区分开发环境和生产环境;
  2. 对外暴露API,相当简洁,常用的API只有五个。
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
1

__DO_NOT_USE__ActionTypes,平时在项目里面我们是不太会用的到,redux的官方文档也没有提到这个,如果我们不看源码,根本不会知道这个东西的存在。里面定义了redux自带的action类型,从这个变量的命名来看,这是帮助开发者检查不要使用redux自带的action的类型,以防出现错误。

function isCrushed() {}
1

上述函数主要作用是: 防止开发者在开发环境下对代码进行压缩。开发环境下压缩代码,会降低我们的开发效率,不利于代码调试。下面给出具体的解释:

压缩前:

function isCrushed() {}
if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
)
1
2
3
4
5
6

压缩后:

function d(){}"string"==typeof d.name&&"isCrushed"!==d.name
1

看到process.env.NODE_ENV,我们很自然地会跟打包时用的环境变量联系起来。分为以下两种情况进行分析:

  1. process.env.NODE_ENV !== 'production'true时,即在生产环境下,warning是不会执行的;
  2. process.env.NODE_ENV !== 'production'false时,即在开发环境下。开发环境下下有可以分成两种情况: 2.1 不压缩代码时typeof isCrushed.name === 'string' && isCrushed.name !== 'isCrushed'不成立,warning是不会执行的; 2.2 压缩代码时isCrushed.name === 'string' && isCrushed.name !== 'isCrushed'是成立的,warning会执行;

createStore.js

// 导入 symbol 类型的 observable (symbol类型的属性,是对象的私有属性)
import $$observable from 'symbol-observable'

import ActionTypes from './utils/actionTypes'
// 判断是否是简单对象
import isPlainObject from './utils/isPlainObject'

/**
 * 创建一个Redux store来存放应用所有的 state。应用中有且只有一个store
 *
 * @param {Function} reducer 是一个函数,接收两个参数,分别是当前的state树和
 * 要处理的action,返回新的state树
 *
 * @param {any} [preloadedState] 初始化时的state,在应用中,你可以把服务端传来经过处理后的state
 * 传给它。如果你使用combineReducers创建reducer,它必须是一个普通对象,与传入
 * 的keys保持同样的结构。否则,你可以自由传入任何reducer可理解的内容。
 *
 * @param {Function} [enhancer] 是一个组合的高阶函数,返回一个强化过的store creator。
 * 这与middleware相似,它也允许你通过复合函数改变store接口。
 *
 * @returns {Store} 返回一个对象,给外部提供 dispatch, getState, subscribe, replaceReducer
 */
export default function createStore(reducer, preloadedState, enhancer) {
    // 如果preloadedState和enhancer都是function,不支持,throw new Error
    // 我们都知道[initState]为object,[enhancer]为function
    // typeof arguments[3] === 'function'为true的话,说明使用了多个store enhancer
    // 需要使用compose()函数将多个store enhancer合并成一个函数
  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'和typeof enhancer === 'undefined'都为true时,说明只有两个参数
  // 且第二个参数为函数,那么实际上传入的是reducer和enhancer,并没有传入初始化的state(preloadedState)
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState // preloadedState形参中实际保存的是enhancer
    preloadedState = undefined
  }
  // typeof enhancer !== 'undefined'为true,则说明传入了三个参数
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') { // 如果有三个参数且第三个参数不为函数则报错(enhancer必须为函数)
      throw new Error('Expected the enhancer to be a function.')
    }
    // 如果传入了applyMiddleware,applyMiddleware的作用就是将这些enhancer格式化成符合redux要求的enhancer
    // 再用处理好的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 // 当前store中的reducer
  let currentState = preloadedState // 当前store中存储的状态
  let currentListeners = [] // 当前store中放置的监听函数,即当前订阅者列表
  let nextListeners = currentListeners // 下一次dispatch时的监听函数列表,即新的订阅列表
  // 注意:当我们新添加一个监听函数时,只会在下一次dispatch的时候生效。
  let isDispatching = false // 标志是否正在dispatch

  // 添加这个函数的意图在下面会讲到,先看代码层面上的作用:
  // 如果nextListeners和currentListeners指向同一个对象,即判断两者是不是同一个引用
  // 则nextListeners对currentListeners进行浅拷贝
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
       // 如果是一个引用的话,浅拷贝出来一个currentListeners赋值给nextListeners
      // 其实这里是保存一份订阅快照
      // slice不修改原数组,只返回一个浅复制了原数组总的元素的一个新数组
      nextListeners = currentListeners.slice()
    }
  }

  /**
   * Reads the state tree managed by the store.
   *
   * @returns {any} store.getState()获取当前应用的state
   */
  function getState() {
      // 为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的
    if (isDispatching) {
        // 参考:https://github.com/reactjs/redux/issues/1568
        // 为了保持reducer的pure,禁止在reducer中调用getState
        // 纯函数reducer要求根据一定的输入即能得到确定的输出,所以禁止了getState,subscribe,unsubscribe和dispatch等会带来副作用的行为
      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.'
      )
    }
    // 返回当前应用的state
    // currentState在每次dispatch的时候都会得到相应的更新
    return currentState
  }

  /**
   * @param {Function} listener A callback to be invoked on every dispatch.
   * @returns {Function} A function to remove this change listener.
   * 添加一个监听函数,每当dispatch被调用的时候都会执行这个监听函数
   */
  function subscribe(listener) {
      // 判断监听者是否为函数。传入的listener必须是一个函数,否则抛出错误
    if (typeof listener !== 'function') {
      throw new Error('Expected the listener to be a function.')
    }
    // 判断是否有reducer正在进行数据修改,保证数据的一致性
    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 // 设置一个标识,标识该监听器已经订阅了
    // 在每次subscribe的时候,浅拷贝一次currentListeners,在nextListener中添加新的listener
    ensureCanMutateNextListeners()
    // 将listener添加到监听函数数组中
    // 需要注意的是:listener在下一次dispatch时才会生效
    nextListeners.push(listener)

    // subscribe函数返回取消订阅的函数,用于从监听函数数组中删除相应的监听函数
    return function unsubscribe() {
       // 判断是否已经取消订阅
      if (!isSubscribed) { // 如果已经取消过订阅了,则直接返回
        return
      }
      // 判断是否有reducer正在进行数据修改,保证数据的一致性
      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 // 设置一个标识,标识该监听器已经取消订阅了
      // 在每次unsubscribe的时候,浅拷贝一次currentListeners,在nextListeners取消订阅当前listener
      ensureCanMutateNextListeners()
      // 找到listener对应的索引
      const index = nextListeners.indexOf(listener)
      // 从nextListeners中删除unsubscribe的listener
      nextListeners.splice(index, 1)
    }
  }

  // 发送action给reducer,返回新的state,并且执行所有添加到store中的监听函数。
  function dispatch(action) {
      // 判断action是否为简单对象
      // action必须是一个plain object,如果想要能处理传进来的函数的话必须使用中间件(redux-thunk等)
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }
    // 判断action.type是否存在,action必须定义type属性
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }
    // 判断当前是否有执行其他的reducer操作
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }
    try {
      isDispatching = true // 执行reducer前,将isDispatching设置为true,阻止后续的action进来触发reducer操作
      currentState = currentReducer(currentState, action) // 调用reducer,获取到新的state,并赋值给currentState
    } finally {
      isDispatching = false // 完成之后在finally里将isDispatching再改为false,允许后续的action进来触发reducer操作
    }
    // 更新监听函数数组
    const listeners = (currentListeners = nextListeners)
    // 依次执行监听函数数组中的监听函数,一一通知订阅者做数据更新,不传入任何参数
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  // 这个函数是用来替换reducer的
  function replaceReducer(nextReducer) {
      // 判断所传reducer是否为函数
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }
    // 通过条件判断之后,将nextReducer赋值给currentReducer,以达到替换reducer效果,并触发state更新操作。
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE }) // 触发相应的action,更新state
  }

  /**
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  // 这块代码我们不需要掌握
  // 这个observable函数,并没有调用,即便暴露出来我们也没办法使用
  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
      }
    }
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  // 为啥要有这么一行代码?
  // 原因很简单,假设我们没有这样代码,此时currentState就是undefined的,也就我说我们没有默认值了
  // 当我们dispatch一个action的时候,就无法在currentState基础上做更新。
  // 所以需要拿到所有reducer默认的state,这样后续的dispatch一个action的时候,才可以更新我们的state。
  dispatch({ type: ActionTypes.INIT })
  // 返回了如下5个方法,其中前3个最为常用
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268

函数createStore接受三个参数(reducer、preloadedState、enhancer),reducer和enhancer我们在项目中用的比较多,preloadedState用的比较少。reducer我们很熟悉,主要用来更改整个应用的状态;preloadedState,它代表初始状态,平时在项目里很少用到它;enhancer,中文名叫增强器,顾名思义就是来增强redux的,它的类型的是Function

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

    return enhancer(createStore)(reducer, preloadedState)
}
1
2
3
4
5
6
7

这行代码展示了enhancer的调用过程,根据这个调用过程我们可以推导出enhancer的函数体的架子应该是这样子的:

function enhancer(createStore) {
    return (reducer, preloadedState) => {
        //逻辑代码
        .......
    }
}
1
2
3
4
5
6

常见的enhancer就是redux-thunk以及redux-saga,一般都会配合applyMiddleware一起使用,而applyMiddleware的作用是:将这些enhancer格式化成符合redux要求的enhancer。具体applyMiddleware实现,下面我们将会讲到。我们先看redux-thunk的使用的例子:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);
1
2
3
4
5
6
7
8

看完上面的代码,可能会有人有这么一个疑问:createStore函数第二个参数不是preloadedState吗?这样不会报错吗?首先肯定不会报错,毕竟官方给的例子,不然写个错误的例子也太大跌眼镜了吧!redux肯定是做了相应的处理的,在createStore.js可以找到如下的代码:

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
}
1
2
3
4

当第二个参数preloadedState是Function并且第三个参数enhancer未定义的时候,说明只传入了两个参数。此时,实际上传入的参数是reducer和enhancer,并没有传入初始化的state(preloadedState)。所以,将preloadedState赋值给enhancer,然后将preloadedState置为undefined的。有了这层处理判断之后,我们就可以大胆地在第二个参数传enhancer了。

isDispatching作用
let isDispatching = false
1

变量isDispatching,作为锁来用,redux是一个统一的管理状态容器,它要保证数据的一致性,所以同一个时间里,只能做一次数据修改。如果两个action同时触发reducer对同一数据的修改,那么将会带来巨大的灾难。所以变量isDispatching就是为了防止这一点而存在的。

getState函数
function getState() {
      // 为了保证数据的一致性,当在reducer操作的时候,是不可以读取当前的state值的
    if (isDispatching) {
        // 参考:https://github.com/reactjs/redux/issues/1568
        // 为了保持reducer的pure,禁止在reducer中调用getState
        // 纯函数reducer要求根据一定的输入即能得到确定的输出,所以禁止了getState,subscribe,unsubscribe和dispatch等会带来副作用的行为
      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.'
      )
    }
    // 返回当前应用的state
    // currentState在每次dispatch的时候都会得到相应的更新
    return currentState
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

需要注意:store通过getState获取到的state是可以直接被更改的,但是redux不允许这么做,因为这样不会通知订阅者更新数据。

ensureCanMutateNextListeners解析
// 如果nextListeners和currentListeners指向同一个对象,即判断两者是不是同一个引用
  // 则nextListeners对currentListeners进行浅拷贝
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
       // 如果是一个引用的话,浅拷贝出来一个currentListeners赋值给nextListeners
      // 其实这里是保存一份订阅快照
      // slice不修改原数组,只返回一个浅复制了原数组总的元素的一个新数组
      nextListeners = currentListeners.slice()
    }
  }
1
2
3
4
5
6
7
8
9
10

逻辑很简单,判断nextListeners和currentListeners是否为同一个引用,还记得dispatch函数中有这么一句代码:

// Function dispatch
const listeners = (currentListeners = nextListeners)
1
2

另外定义变量时也有如下代码:

// 定义变量
let currentListeners = []
let nextListeners = currentListeners
1
2
3

上述两处代码将nextListeners和currentListeners引用了同一个数组。而ensureCanMutateNextListeners就是用来判断这种情况的,当nextListeners和currentListeners为同一个引用时,则做一层浅拷贝,这里用的是Array.prototype.slice方法,该方法会返回一个新的数组,这样就可以达到浅拷贝的效果。

函数ensureCanMutateNextListeners作为处理之后,将新的订阅者加入nextListeners中,并且返回取消订阅的函数unsubscribe

看到这里可能有小伙伴们对currentListeners和nextListeners有这么一个疑问?函数dispatch里面将二者合并成一个引用,为啥这里又分开?直接用currentListeners不可以吗?这里这样做其实也是为了数据的一致性,因为有这么一种的情况存在:当redux在通知所有订阅者的时候,此时又有一个新的订阅者加进来了。如果只用currentListeners的话,当新的订阅者插进来的时候,就会打乱原有的顺序,从而引发一些严重的问题。

dispatch({ type: ActionTypes.INIT })
1

为啥要有这么一行代码?原因很简单,假设我们没有这样代码,此时currentState就是undefined的,也就是说我们没有默认值了,当我们dispatch一个action的时候,就无法在currentState基础上做更新。所以需要拿到所有reducer默认的state,这样后续的dispatch一个action的时候,才可以更新我们的state

applyMiddleware.js

import compose from './compose'

export default function applyMiddleware(...middlewares) { // middlewares是一个中间件数组
    // 返回值是一个返回函数的函数(其实就是一个enhancer)
    // 外层函数的参数是createStore函数
    // 内层函数其实是一个加强版的createStore函数
    return createStore => (...args) => {
        // 这里是内层函数的函数体
        const store = createStore(...args) // 创建一个store
        let dispatch = () => { // 定义一个临时的dispatch,如果在中间件构造过程中调用dispatch,会抛出错误信息
            throw new Error(
                `Dispatching while constructing your middleware is not allowed. ` +
                `Other middleware would not be applied to this dispatch.`
            )
        }
        // 定义middlewareAPI,包含两个方法,一个是getState,另一个是dispatch
        // 传给middleware的参数
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
        // 执行所有的middleware
        // middlewares调用Array.prototype.map进行改造,存放在chain
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        // 用compose整合chain数组,并赋值给dispatch,重新改写dispatch
        dispatch = compose(...chain)(store.dispatch)

        return {
            ...store,
            dispatch // 将新的dispatch替换原先的store.dispatch
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

具体实现逻辑:

  1. 通过createStore方法创建出一个store
  2. 定一个dispatch,如果在中间件构造过程中调用,抛出错误提示;
  3. 定义middlewareAPI,有两个方法,一个是getState,另一个是dispatch,将其作为中间件调用的store的桥接;
  4. middlewares调用Array.prototype.map进行改造,存放在chain
  5. compose整合chain数组,并赋值给dispatch
  6. 将新的dispatch替换原先的store.dispatch

前面我们讲enhancer的时候,提到过这个applyMiddleware,现在我们将二者的格式对比看一下。

// enhancer
 function enhancer(createStore) {
    return (reducer,preloadedState) => {
         //逻辑代码
        .......
    }
 }
//applyMiddleware
function //applyMiddleware(...middlewares) {
    return createStore => (...args) => {
        //逻辑代码
        .......
    }
 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14

通过二者的对比,我们发现函数applyMiddleware的返回值其实就是一个enhancer

看完上述的分析,大家可能还有很多疑问(比如刚刚读完的我)。变量chain是什么?dispatch是如何被改造的?下面我们以redux-thunk为例,模拟一下整个过程。redux-thunk的源码如下:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

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

export default thunk;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我们最终得到的thunk是createThunkMiddleware()处理过的thunk,再看createThunkMiddleware这个函数,返回的是一个返回函数的函数。

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}
1
2
3
4
5
6
7
8
9

我们将上述代码编译成ES5的代码:

function createThunkMiddleware(extraArgument) {
    return function({ dispatch, getState }) {
      // 这里返回的函数就是chain
      return function(next) {
        // 这里返回的函数就是改写的dispatch
        return function(action) {
          if (typeof action === 'function') {
              return action(dispatch, getState, extraArgument);
          }

          return next(action);
        };
      }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

到这里我们就知道变量chain就是以next作为形参的匿名函数。compose函数作用是:将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。这里chain只是一个函数,所以很简单,就是执行chain,并将store.dispatch作为实参传递给next

改造后的dispatch最终变为:

function(action) {
  if (typeof action === 'function') {
    return action(dispatch, getState, extraArgument);
  }
  // next为之前传入的store.dispatch,即改写前的dispatch
  return next(action);
};
1
2
3
4
5
6
7

如果传入的action是函数,则执行函数;否则直接dispatch(action)

compose.js

compose.js函数主要作用是:将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。以烹饪为例,每道菜都是从最初的食材经过一道又一道的工序处理才得到的。compose的用处就可以将这些烹饪工序连接到一起,我们只需要提供食材,它会自动帮你经过一道又一道的工序处理,烹饪出这道菜。

源码如下:

export default function compose(...funcs) {
    // 当funcs长度为0时,返回一个传入什么就返回什么的函数
    if (funcs.length === 0) {
        return arg => arg
    }
    // 当funcs长度为1时,返回funcs中的第一项对应的函数
    if (funcs.length === 1) {
        return funcs[0]
    }
    // 当funcs长度大于1时,调用Array.prototype.reduce方法进行整合
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
1
2
3
4
5
6
7
8
9
10
11
12

这里的compose有个特点,他不是从左到右执行的,而是从右到左执行的,下面我们看个例子:

const value = compose(function(value) {
  return value + 1;
}, function(value) {
    return value * 2;
}, function(value) {
    return value - 3;
})(2);
console.log(value); // (2-3)*2+1=-1
1
2
3
4
5
6
7
8

如果想要其从左向右执行也很简单,做一下顺序的颠倒即可。

===> 转换前 return a(b.apply(undefined, arguments));
===> 转换后 return b(a.apply(undefined, arguments));
1
2

源码对应的es5版本:

function compose() {
  var _len = arguments.length;
  var funcs = [];
  for (var i = 0; i < _len; i++) {
    funcs[i] = arguments[i];
  }

  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

梳理一下整个流程,大致如下几步:

  1. 新建一个新数组funcs,将arguments里面的每一项一一拷贝到funcs中去;
  2. funcs的长度为0时,返回一个传入什么就返回什么的函数;
  3. funcs的长度为1时,返回funcs0项对应的函数;
  4. funcs的长度大于1时,调用Array.prototype.reduce方法进行整合。

Array.prototype.reduce()方法

reduce()方法对累计器和数组中的每个元素(从左到右)应用一个函数,将其简化为单个值。

reducer 函数接收4个参数:

  1. Accumulator (acc) (累计器);
  2. Current Value (cur) (当前值);
  3. Current Index (idx) (当前索引);
  4. Source Array (src) (源数组)。

reducer函数的返回值分配给累计器,其值在数组的每个迭代中被记住,并最后成为最终的单个结果值。

语法如下:

arr.reduce(callback, [initialValue])
1

参数:

callback

callback执行数组中每个值的函数,包含四个参数:

  • accumulator: 累计器累计回调的返回值;它是上一次调用回调时返回的累积值,或initialValue;
  • currentValue: 数组中正在处理的元素;
  • currentIndex(可选): 数组中正在处理的当前元素的索引。如果提供了initialValue,则起始索引号为0,否则为1
  • array(可选): 调用reduce()的数组。
initialValue(可选)

作为第一次调用callback函数时的第一个参数的值。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce将报错。

函数说明如下

回调函数第一次执行时,accumulator和currentValue的取值有两种情况:

  1. 如果调用reduce()时提供了initialValueaccumulator取值为initialValuecurrentValue取数组中的第一个值;
  2. 如果没有提供initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值。

注意:如果没有提供initialValuereduce会从索引1的地方开始执行 callback方法,跳过第一个索引。如果提供initialValue,从索引0开始。

如果数组为空且没有提供initialValue,会抛出TypeError。如果数组仅有一个元素(无论位置如何)并且没有提供initialValue,或者有提供initialValue但是数组为空,那么此唯一值将被返回并且callback不会被执行。

reduce如何运行

没有提供initialValue

[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
  return accumulator + currentValue;
});
1
2
3

dd9ee7bac648a848a090ddd2dcd1c747.png

由上图可以看出:没有提供initialValue,那么accumulator取数组中的第一个值,currentValue取数组中的第二个值,currentIndex为1。

提供initialValue:10

[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => {
    return accumulator + currentValue;
}, 10);
1
2
3

f3a7bfec6b6624108e2469ae509f8f1b.png

由上图可以看出:提供initialValue10,那么accumulatorinitialValue的值10,currentValue取数组中的第一个值,currentIndex为0。

特别说明:Array.prototype.reduceRight()函数:接受一个函数作为累加器(accumulator)和数组的每个值(从右到左)将其减少为单个值。Array.prototype.reduce()从左到右

combineReducers.js

combineReducers.js对应着redux里的combineReducers方法,主要作用就是合并多个reducer

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

// 异常检测,找出state里面没有对应reducer的key,并提示开发者做调整
function getUnexpectedStateShapeWarningMessage(
  inputState,
  reducers,
  action,
  unexpectedKeyCache
) {
  const reducerKeys = Object.keys(reducers) // 获取所有的有效Reducer对应的key
  const argumentName =
    action && action.type === ActionTypes.INIT
      ? 'preloadedState argument passed to createStore'
      : 'previous state received by the reducer'

  if (reducerKeys.length === 0) { // 判断reducers是否为{}空对象
    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)) { // 判断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('", "')}"`
    )
  }
  // 筛选出inputState里有的key,但reducers里没有的key
  const unexpectedKeys = Object.keys(inputState).filter(
    key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
  )

  unexpectedKeys.forEach(key => {
    unexpectedKeyCache[key] = true
  })
  // 如果是替换reducer的action,则跳过,不打印异常信息
  if (action && action.type === ActionTypes.REPLACE) return

  // 将所有异常的key打印出来
  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.`
    )
  }
}

// 检测finalReducers里的每个reducer是否都有默认返回值
function assertReducerShape(reducers) { // 接收所有有效的reducer
  Object.keys(reducers).forEach(key => {
    const reducer = reducers[key]
    // 判断finalReducer中的reducer接受一个初始化action,是否依旧能够返回有效的state
    const initialState = reducer(undefined, { type: ActionTypes.INIT })

    if (typeof initialState === 'undefined') { // 返回无效的state,则抛出错误
      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.`
      )
    }
    // 判断finalReducer中的reducer接受一个未知的action,是否依旧能够返回有效的state
    if (
      typeof reducer(undefined, {
        type: ActionTypes.PROBE_UNKNOWN_ACTION()
      }) === 'undefined'
    ) {
      throw new Error( // 返回无效的state,则抛出错误
        `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.`
      )
    }
  })
}

// reducers  Object类型 每个属性对应的值都要是function
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers) // 获取传入的reducers对象的所有key
  const finalReducers = {} // 存储真正有效的reducer
  // 从传入的reducers中筛选出有效的reducer
  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存入finalReducers对应的key中
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers) // 获取有效的reducers对象的所有key

  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {} // 开发模式下
  }

  let shapeAssertionError
  // 这里assertReducerShape函数做的事情是:
  // 检查finalReducer中的reducer接受一个初始action或一个未知的action时,是否依旧能够返回有效的值。
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }
  // 返回一个函数,用于代理所有的reducer
  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 // 标识state是否发生变化
    const nextState = {} // 存储新的state
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key] // 依次获取所有的子reducer
      const previousStateForKey = state[key] // 获取到子reducer对应的旧state
      const nextStateForKey = reducer(previousStateForKey, action) // 调用reducer得到新状态
      // 对新的state是否为undefined的作校验
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey // 将得到的新状态存入nextState中
      // 只要有一个子reducer对应的state发生了变化,hasChanged就为true
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state // state发生变化,返回nextState,否则返回state
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173

assertReducerShape方法主要检测两点:

  1. 不能占用<redux/*>的命名空间;
  2. 如果遇到未知的action的类型,不需要用默认返回值。
  • 如果传入type@@redux/INIT<随机值>action,返回undefined,说明没有对初始化的action的类型做响应,需要给对应的reducer加默认值。
  • 如果对应type@@redux/PROBE_UNKNOWN_ACTION_<随机值>返回为undefined,说明占用了<redux/*>命名空间。整个逻辑相对简单,好好自己梳理一下。

bindActionCreators.js

版本为:v4.0.1

function bindActionCreator(actionCreator, dispatch) {
    return function() {
        // actionCreators必须返回一个action
        return dispatch(actionCreator.apply(this, arguments))
    }
}

// bindActionCreators一般需要结合react-redux一起使用
export default function bindActionCreators(actionCreators, dispatch) {
    // 当actionCreators是函数时
    if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
    }
    // 判断actionCreators是否为null或者非对象
    // 提示开发者actionCreators类型错误,应该是一个非空对象或者是函数。
    if (typeof actionCreators !== 'object' || actionCreators === null) {
        throw new Error(
        `bindActionCreators expected an object or a function, instead received ${
            actionCreators === null ? 'null' : typeof actionCreators
        }. ` +
            `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
        )
    }
    // actionCreators是对象的情况
    const keys = Object.keys(actionCreators)
    const boundActionCreators = {}
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i]
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
            // boundActionCreators中的每一个key都对应dispatch(actionCreator.apply(this, arguments))
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
    }
    return boundActionCreators
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

Redux典型应用

const initialState = {
  cash: 200 // 小金库初始金额
}
const reducer = (state = initialState, action) => {
  const {type, payload} = action;
  switch (type) {
    case 'INCREMENT':
      return Object.assign({}, state, {
        cash: state.cash + payload
      });
    case 'DECREMENT':
      return Object.assign({}, state, {
        cash: state.cash - payload
      });
    default:
      return state;
  }
}

const reducers = Redux.combineReducers({treasury: reducer});

// 创建小金库
const store = Redux.createStore(reducers);
console.log(store.getState());

// 当小金库的现金发生变化时,打印当前的金额
store.subscribe(() => {
  console.log(store.getState());
  console.log(`余额:${store.getState().treasury.cash}`);
});

// 小明爸爸发了工资300块上交
store.dispatch({
  type: 'INCREMENT',
  payload: 300
});
// 小明拿着水电费单交100块水电费
store.dispatch({
  type: 'DECREMENT',
  payload: 100
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41