在前面 Redux 系列一:基础概念中,我们说到了 Redux 奉行的三个原则:单一数据源、State 只读和 State 数据状态必须通过 reducer 纯函数进行修改。这三个原则都是与 Redux 所管理的 Store 和 State 息息相关的。接下来的内容,我们就好好说道说道吧!

为了方便大家更好的理解关于这部分的内容,先放出一张 Redux 的流程图

image.png

Store

在上面的流程图中,我们可以很容易的看出:Store 算是整个 Redux 功能的中心了,reducer 纯函数可以通过传过来的 action 修改 Store 里面的数据状态,Store 里面的数据状态又可以影响到 view 视图,利用新的数据状态是 view 进行重绘。

在 Redux 中,Store 是用来存储应用所有的数据状态 —— state 的。根据前面说到的 Redux 奉行的原则之一:单一数据源,可以知道整个应用只有一个 Store。

Redux 提供了 createStore 函数用来生成 Store,这部分的源码定义在 Redux 项目下的 src/createStore.js 文件,删除注释和无关紧要的代码展示如下(当然为了更好的理解作者的设计思路,我还是建议大家把项目代码拉下来对照着查看):

  1. export default function createStore(reducer, preloadedState, enhancer) {
  2. if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
  3. enhancer = preloadedState
  4. preloadedState = undefined
  5. }
  6. if (typeof enhancer !== 'undefined') {
  7. if (typeof enhancer !== 'function') {
  8. throw new Error('Expected the enhancer to be a function.')
  9. }
  10. return enhancer(createStore)(reducer, preloadedState)
  11. }
  12. if (typeof reducer !== 'function') {
  13. throw new Error('Expected the reducer to be a function.')
  14. }
  15. let currentReducer = reducer
  16. let currentState = preloadedState
  17. let currentListeners = []
  18. let nextListeners = currentListeners
  19. let isDispatching = false
  20. function ensureCanMutateNextListeners() {
  21. if (nextListeners === currentListeners) {
  22. nextListeners = currentListeners.slice()
  23. }
  24. }
  25. function getState() {
  26. if (isDispatching) {
  27. throw new Error(
  28. 'You may not call store.getState() while the reducer is executing. ' +
  29. 'The reducer has already received the state as an argument. ' +
  30. 'Pass it down from the top reducer instead of reading it from the store.'
  31. )
  32. }
  33. return currentState
  34. }
  35. function subscribe(listener) {
  36. if (typeof listener !== 'function') {
  37. throw new Error('Expected the listener to be a function.')
  38. }
  39. if (isDispatching) {
  40. throw new Error(
  41. 'You may not call store.subscribe() while the reducer is executing. ' +
  42. 'If you would like to be notified after the store has been updated, subscribe from a ' +
  43. 'component and invoke store.getState() in the callback to access the latest state. ' +
  44. 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
  45. )
  46. }
  47. let isSubscribed = true
  48. ensureCanMutateNextListeners()
  49. nextListeners.push(listener)
  50. return function unsubscribe() {
  51. if (!isSubscribed) {
  52. return
  53. }
  54. if (isDispatching) {
  55. throw new Error(
  56. 'You may not unsubscribe from a store listener while the reducer is executing. ' +
  57. 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
  58. )
  59. }
  60. isSubscribed = false
  61. ensureCanMutateNextListeners()
  62. const index = nextListeners.indexOf(listener)
  63. nextListeners.splice(index, 1)
  64. }
  65. }
  66. function dispatch(action) {
  67. if (!isPlainObject(action)) {
  68. throw new Error(
  69. 'Actions must be plain objects. ' +
  70. 'Use custom middleware for async actions.'
  71. )
  72. }
  73. if (typeof action.type === 'undefined') {
  74. throw new Error(
  75. 'Actions may not have an undefined "type" property. ' +
  76. 'Have you misspelled a constant?'
  77. )
  78. }
  79. if (isDispatching) {
  80. throw new Error('Reducers may not dispatch actions.')
  81. }
  82. try {
  83. isDispatching = true
  84. currentState = currentReducer(currentState, action)
  85. } finally {
  86. isDispatching = false
  87. }
  88. const listeners = (currentListeners = nextListeners)
  89. for (let i = 0; i < listeners.length; i++) {
  90. const listener = listeners[i]
  91. listener()
  92. }
  93. return action
  94. }
  95. function replaceReducer(nextReducer) {
  96. if (typeof nextReducer !== 'function') {
  97. throw new Error('Expected the nextReducer to be a function.')
  98. }
  99. currentReducer = nextReducer
  100. dispatch({ type: ActionTypes.REPLACE })
  101. }
  102. function observable() {
  103. const outerSubscribe = subscribe
  104. return {
  105. subscribe(observer) {
  106. if (typeof observer !== 'object' || observer === null) {
  107. throw new TypeError('Expected the observer to be an object.')
  108. }
  109. function observeState() {
  110. if (observer.next) {
  111. observer.next(getState())
  112. }
  113. }
  114. observeState()
  115. const unsubscribe = outerSubscribe(observeState)
  116. return { unsubscribe }
  117. },
  118. [$$observable]() {
  119. return this
  120. }
  121. }
  122. }
  123. dispatch({ type: ActionTypes.INIT })
  124. return {
  125. dispatch,
  126. subscribe,
  127. getState,
  128. replaceReducer,
  129. [$$observable]: observable
  130. }
  131. }

createStore 方法可接受一个到三个参数。首先是必传的用来描述数据状态变化逻辑的纯函数 reducer,如果你只传了这一个参数,那你就应该在 reducer 中处理初始数据状态的值,以便在默认逻辑下返回有效的数据状态,形如:

function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

这段示例代码就是一个简单的 reducer 逻辑,它用利用 ES6 的形式给 state 参数设置了默认值,以便在 switch…case 逻辑段中的 default 逻辑返回正确、有效的数据状态供使用。

当然,如果你不喜欢这种处理方式,你可以在调用 createStore 时传递第二个参数。第二个参数 preloadedState 会接受你设置的默认数据状态。如果你传递了有效数据给这个参数,那在实际处理 reducer 逻辑是会忽略你通过ES6 形式给 state 参数设置的默认值。不信你可以看看代码逻辑,如果你传递了有效状态给 preloadedState ,那最终调用 currentReducer 时,传递的 preloadedState 值就是有效的。根据 ES6 设置函数默认参数值的特性(默认值只会在参数接受 undefined 时有效),你设置的默认参数值就不会生效了。
**
除了上面介绍到的 reducer 和 preloadedState 参数外,createStore 还可以接受用来增强 Store (实现类似时间旅行和状态持久化)的第三个参数 enhancer,说白了就是我们后续要介绍到的中间件 MiddleWare,这里我们不单独拧出来说了。

createStore 函数的调用会产生一个 Store,它会包含有一些方法供我们在开发过程中进行使用:

  • dispatch:dispatch 是一个函数,会接受一个 action 作为参数。当需要修改 state 数据状态是,我们会用它来发射一个 action 到 reducer,形如:store.dispatch(action);

  • subscribe:subscribe 函数会接受一个 listener 函数,用来实现订阅,主要作用是当 state 发生变化后,依次调用所有订阅的 listener 函数,实现对 state 状态数据的监听操作逻辑,形如 store.subscribe(listener)。需要特别值得注意的是,该方法的调用会返回一个取消监听的方法,如果你在某一时刻调用了这个方法,就会取消该 listener 函数对 state 状态数据的监听;

  • getState:用来获取当前 Store 中所有最新的数据状态 state,形如 store.getState();

  • replaceReducer:在有些时候由于业务逻辑的复用关系,我们需要复用之前的一套逻辑代码,但是对于 reducer 这个修改 Store 里面数据状态的逻辑又有所不同,再加上我们之前一直强调的:整个应用只有一个数据源,也就是只有一个 Store,所以我们不可能在不同的业务场景重复调用 createStore 根据新的 reducer 创建不同的 Store,所以这个方法就派上用场了。replaceReducer 方法接受一个新的 reducer 纯函数,在后续通过调用 store.dispatch(action) 时就会进入这个新的 reducer 纯函数逻辑进行处理。

说到这里,大家再回头看看上面我们给出的流程图,是不是豁然开朗。而关于这些方法的具体实现,大家可以参考文案和代码进行理解,这里我就不赘述了。

State

Store 就像是一个超级市场,它会提供购物车、摆货员去操作那个商品,而 State 就是超级市场里面的商品了。关于这个商品,我们需要进行合理的定义和描述,这样才能满足购物者(用户)的需求和购买欲望。在 Redux 的 Store 中,State 是 Store 中包含的所有数据在某一个时间点的快照,也就是数据集合,而这个数据集合和 view 是一一对应的关系,只要 State 相同,View 就相同,反之,知道了当前 view 的样子就知道了 State 的数据。

为了对 State 修改的强约束,即修改 State 必须通过调用 dispatch 发射一个 action 到 reducer,Redux 奉行了 State 只读和 State 数据状态必须通过 reducer 纯函数进行修改这两条原则。

  • State 只读:从源代码来看,除了我们可以在定义 reducer 和调用 createStore 时传入初始化的 State 数据外,在其他任何逻辑里面我们是不能直接去修改 State 的。如果想要修改 State,我们必须通过 Redux 约定的那一套流程;

  • 必须通过 reducer 纯函数进行修改:纯函数是什么概念?即相同的输入,就有相同的输出。上面我们已经说到修改 State 必须通过调用 dispatch 发射一个 action 到 reducer。在 reducer 中,Redux 约定它是一段不包含副作用的逻辑代码,即纯函数。并且对于 State 的修改,不应该是在原有的 State 数据上进行修改,而是返回全新的数据,你可以借助 ES6 提供的 Object.assign 函数和对象展开符。

以上就是修改 State 部分了,关于获取应用的 State,上面有提到可以通过 store.getState() 进行获取。除了这写之外,关于 State 数据结构的定义对于应用的可扩展和可维护也是极其重要的,大家可以参考 Redux进阶系列2:如何合理地设计StateRedux 官网或根据自己业务的实际场景进行合理的划分和定义。

总结

针对前一专栏提到的 Redux 三大原则结合本专栏的 Store 和 State 进行了展开说明,并结合源码对 store 构建方法 createStore 做了详细的说明,下一个专栏我们将对 Action 和 CreateAction 进行相关的说明。