是什么(What)?
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。Redux 由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂性。Redux 除了和 React 一起用外,还支持其它 UI 库。 它体小精悍(只有 2kB,包括依赖)。
为什么(Why)?
现代 JavaScript 应用中,需要管理比任何时候都要多的 state (状态)。管理不断变化的 state 非常困难,state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。正是这样,Redux 试图让 state 的变化变得可预测。
怎么样(How)?
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。
看个简单的例子!
1 | import { createStore } from 'redux'; |
你应该把要做的修改变成一个普通对象,这个对象被叫做 action
,而不是直接修改 state。然后编写专门的函数来决定每个 action 如何改变应用的 state,这个函数被叫做 reducer
。
如果你以前使用 Flux,那么你只需要注意一个重要的区别。Redux 没有 Dispatcher 且不支持多个 store。相反,只有一个单一的 store 和一个根级的 reduce 函数(reducer)。随着应用不断变大,你应该把根级的 reducer 拆成多个小的 reducers,分别独立地操作 state 树的不同部分,而不是添加新的 stores。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。
核心概念
Redux 本身很简单。
当使用普通对象来描述应用的 state
时。例如,todo 应用的 state
可能长这样:
1 | { |
这个对象就像 Model
,区别是它并没有 setter
(修改器方法)。因此其它的代码不能随意修改它,造成难以复现的 bug。
要想更新 state 中的数据,你需要发起一个 action。Action 就是一个普通 JavaScript 对象(注意到没,这儿没有任何魔法?)用来描述发生了什么。下面是一些 action 的示例:
1 | { type: 'ADD_TODO', text: 'Go to swimming pool' } |
强制使用 action
来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。如果一些东西改变了,就可以知道为什么变。action
就像是描述发生了什么的指示器。最终,为了把 action 和 state 串起来,开发一些函数,这就是 reducer
。再次地,没有任何魔法,reducer 只是一个接收 state 和 action,并返回新的 state 的函数。 对于大的应用来说,不大可能仅仅只写一个这样的函数,所以我们编写很多小函数来分别管理 state 的一部分:
1 | function visibilityFilter(state = 'SHOW_ALL', action) { |
再开发一个 reducer
调用这两个 reducer,进而来管理整个应用的 state:
1 | function todoApp(state = {}, action) { |
这差不多就是 Redux
思想的全部。注意到没我们还没有使用任何 Redux 的 API。Redux 里有一些工具来简化这种模式,但是主要的想法是如何根据这些 action 对象来更新 state,而且 90% 的代码都是纯 JavaScript,没用 Redux、Redux API 和其它魔法。
三大原则
单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
这让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。
1 | console.log(store.getState()) |
State 只读
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
1 | store.dispatch({ |
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 [reducers][3]。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分,因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。
1 | function visibilityFilter(state = 'SHOW_ALL', action) { |