§ createStore(reducer, initialState, enhancer)

⊙ 源码分析

  1. import isPlainObject from 'lodash/isPlainObject'
  2. import $$observable from 'symbol-observable'
  3. /**
  4. * 这是 Redux 的私有 action 常量
  5. * 长得太丑了,你不要鸟就行了
  6. */
  7. export var ActionTypes = {
  8. INIT: '@@redux/INIT'
  9. }
  10. /**
  11. * @param {函数} reducer 不多解释了
  12. * @param {对象} preloadedState 主要用于前后端同构时的数据同步
  13. * @param {函数} enhancer 很牛逼,可以实现中间件、时间旅行,持久化等
  14. * ※ Redux 仅提供 applyMiddleware 这个 Store Enhancer ※
  15. * @return {Store}
  16. */
  17. export default function createStore(reducer, preloadedState, enhancer) {
  18. // 这里省略的代码,到本文的最后再讲述(用于压轴你懂的)
  19. var currentReducer = reducer
  20. var currentState = preloadedState // 这就是整个应用的 state
  21. var currentListeners = [] // 用于存储订阅的回调函数,dispatch 后逐个执行
  22. var nextListeners = currentListeners //【悬念1:为什么需要两个 存放回调函数 的变量?】
  23. var isDispatching = false
  24. /**
  25. * 【悬念1·解疑】
  26. * 试想,dispatch 后,回调函数正在乖乖地被逐个执行(for 循环进行时)
  27. * 假设回调函数队列原本是这样的 [a, b, c, d]
  28. *
  29. * 现在 for 循环执行到第 3 步,亦即 a、b 已经被执行,准备执行 c
  30. * 但在这电光火石的瞬间,a 被取消订阅!!!
  31. *
  32. * 那么此时回调函数队列就变成了 [b, c, d]
  33. * 那么第 3 步就对应换成了 d!!!
  34. * c 被跳过了!!!这就是躺枪。。。
  35. *
  36. * 作为一个回调函数,最大的耻辱就是得不到执行
  37. * 因此为了避免这个问题,本函数会在上述场景中把
  38. * currentListeners 复制给 nextListeners
  39. *
  40. * 这样的话,dispatch 后,在逐个执行回调函数的过程中
  41. * 如果有新增订阅或取消订阅,都在 nextListeners 中操作
  42. * 让 currentListeners 中的回调函数得以完整地执行
  43. *
  44. * 既然新增是在 nextListeners 中 push,因此毫无疑问
  45. * 新的回调函数不会在本次 currentListeners 的循环体中被触发
  46. *
  47. * (上述事件发生的几率虽然很低,但还是严谨点比较好)
  48. */
  49. function ensureCanMutateNextListeners() { // <-------这货就叫做【ensure 哥】吧
  50. if (nextListeners === currentListeners) {
  51. nextListeners = currentListeners.slice()
  52. }
  53. }
  54. /**
  55. * 返回 state
  56. */
  57. function getState() {
  58. return currentState
  59. }
  60. /**
  61. * 负责注册回调函数的老司机
  62. *
  63. * 这里需要注意的就是,回调函数中如果需要获取 state
  64. * 那每次获取都请使用 getState(),而不是开头用一个变量缓存住它
  65. * 因为回调函数执行期间,有可能有连续几个 dispatch 让 state 改得物是人非
  66. * 而且别忘了,dispatch 之后,整个 state 是被完全替换掉的
  67. * 你缓存的 state 指向的可能已经是老掉牙的 state 了!!!
  68. *
  69. * @param {函数} 想要订阅的回调函数
  70. * @return {函数} 取消订阅的函数
  71. */
  72. function subscribe(listener) {
  73. if (typeof listener !== 'function') {
  74. throw new Error('Expected listener to be a function.')
  75. }
  76. var isSubscribed = true
  77. ensureCanMutateNextListeners() // 调用 ensure 哥保平安
  78. nextListeners.push(listener) // 新增订阅在 nextListeners 中操作
  79. // 返回一个取消订阅的函数
  80. return function unsubscribe() {
  81. if (!isSubscribed) {
  82. return
  83. }
  84. isSubscribed = false
  85. ensureCanMutateNextListeners() // 调用 ensure 哥保平安
  86. var index = nextListeners.indexOf(listener)
  87. nextListeners.splice(index, 1) // 取消订阅还是在 nextListeners 中操作
  88. }
  89. }
  90. /**
  91. * 改变应用状态 state 的不二法门:dispatch 一个 action
  92. * 内部的实现是:往 reducer 中传入 currentState 以及 action
  93. * 用其返回值替换 currentState,最后逐个触发回调函数
  94. *
  95. * 如果 dispatch 的不是一个对象类型的 action(同步的),而是 Promise / thunk(异步的)
  96. * 则需引入 redux-thunk 等中间件来反转控制权【悬念2:什么是反转控制权?】
  97. *
  98. * @param & @return {对象} action
  99. */
  100. function dispatch(action) {
  101. if (!isPlainObject(action)) {
  102. throw new Error(
  103. 'Actions must be plain objects. ' +
  104. 'Use custom middleware for async actions.'
  105. )
  106. }
  107. if (typeof action.type === 'undefined') {
  108. throw new Error(
  109. 'Actions may not have an undefined "type" property. ' +
  110. 'Have you misspelled a constant?'
  111. )
  112. }
  113. if (isDispatching) {
  114. throw new Error('Reducers may not dispatch actions.')
  115. }
  116. try {
  117. isDispatching = true
  118. // 关键点:currentState 与 action 会流通到所有的 reducer
  119. // 所有 reducer 的返回值整合后,替换掉当前的 currentState
  120. currentState = currentReducer(currentState, action)
  121. } finally {
  122. isDispatching = false
  123. }
  124. // 令 currentListeners 等于 nextListeners,表示正在逐个执行回调函数(这就是上面 ensure 哥的判定条件)
  125. var listeners = currentListeners = nextListeners
  126. // 逐个触发回调函数
  127. for (var i = 0; i < listeners.length; i++) {
  128. listeners[i]()
  129. /* 现在逐个触发回调函数变成了:
  130. var listener = listeners[i]
  131. listener() // 该中间变量避免了 this 指向 listeners 而造成泄露的问题 */
  132. // 在此衷心感谢 @BuptStEve 在 issue7 中指出之前我对此处的错误解读
  133. }
  134. return action // 为了方便链式调用,dispatch 执行完毕后,返回 action(下文会提到的,稍微记住就好了)
  135. }
  136. /**
  137. * 替换当前 reducer 的老司机
  138. * 主要用于代码分离按需加载、热替换等情况
  139. *
  140. * @param {函数} nextReducer
  141. */
  142. function replaceReducer(nextReducer) {
  143. if (typeof nextReducer !== 'function') {
  144. throw new Error('Expected the nextReducer to be a function.')
  145. }
  146. currentReducer = nextReducer // 就是这么简单粗暴!
  147. dispatch({ type: ActionTypes.INIT }) // 触发生成新的 state 树
  148. }
  149. /**
  150. * 这是留给 可观察/响应式库 的接口(详情 https://github.com/zenparsing/es-observable)
  151. * 如果您了解 RxJS 等响应式编程库,那可能会用到这个接口,否则请略过
  152. * @return {observable}
  153. */
  154. function observable() {略}
  155. // 这里 dispatch 只是为了生成 应用初始状态
  156. dispatch({ type: ActionTypes.INIT })
  157. return {
  158. dispatch,
  159. subscribe,
  160. getState,
  161. replaceReducer,
  162. [$$observable]: observable
  163. }
  164. }

【悬念2:什么是反转控制权? · 解疑】
在同步场景下,dispatch(action) 的这个 action 中的数据是同步获取的,并没有控制权的切换问题
但异步场景下,则需要将 dispatch 传入到回调函数。待异步操作完成后,回调函数自行调用 dispatch(action)

说白了:在异步 Action Creator 中自行调用 dispatch 就相当于反转控制权
您完全可以自己实现,也可以借助 [redux-thunk][redux-thunk] / [redux-promise][redux-promise] 等中间件统一实现
(它们的作用也仅仅就是把 dispatch 等传入异步 Action Creator 罢了)

拓展阅读:阮老师的 [Thunk 函数的含义与用法][ryf-thunk]
题外话:您不觉得 JavaScript 的回调函数,就是反转控制权最普遍的体现吗?