为什么需要 Redux?
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state(状态)。这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否加载动画或者分页器等。
管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次引起另一个 view 的变化。直到你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已不受控制。当系统变得错综复杂的时候,想重现问题就会变得举步维艰。
如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等。前端开发者正在经受前所未有的复杂性。
这里的复杂性很大程度来自于:我们总是将两个难以理清的概念混淆在一起:变化和异步。如果只是同步地修改 state,或者进行异步操作而不修改 state,看起来并没有麻烦。但一旦将这两者混合在一起,你便无法确定一个函数调用后会有多少个异步操作进行,以及何时返回结果修改 state,APP 的行为将变得非常难以预测,并且很难调试。React 试图在视图层禁止异步和直接操作 DOM 来解决这个问题。但是 React 把管理 state 中数据的问题交给了我们。Redux 就是来帮助我们解决这个问题。
核心概念
Redux 本身很简单。
当使用普通对象来描述应用得 state 时。例如,TODO 应用得 state 可能长这样:
{
todos: [{
text: 'Eat food',
completed: true
}, {
text: 'Exercise',
completed: false
}],
visibilityFilter: 'SHOW_COMPLETED'
}
这个对象就像 “Model”,区别是它并没有 setter(修改器方法)。因此其它的代码不能随意修改它,造成难以复现的 bug。
要想更新 state 中的数据,你需要发起一个 action。Action 就是一个普通 JavaScript 对象(注意到没,这儿没有任何魔法?)用来描述发生了什么。下面是一些 action 的示例:
{ type: 'ADD_TODO', text: 'Go to swimming pool' }
{ type: 'TOGGLE_TODO', index: 1 }
{ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
强制使用 action 来描述所有变化带来的好处是可以清晰地知道应用中到底发生了什么。 如果一些东西改变了,就可以知道为什么变。action 就像是描述发生了什么的指示器。最终,为了把 action 和 state 串起来,开发一些函数,这就是 reducer。再次地,没有任何魔法,reducer 只是一个接收 state 和 action,并返回新的 state 的函数。 对于大的应用来说,不大可能仅仅只写一个这样的函数,所以我们编写很多小函数来分别管理 state 的一部分:
function visibilityFilter(state = 'SHOW_ALL', action) {
if (action.type === 'SET_VISIBILITY_FILTER') {
return action.filter
} else {
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return state.concat([{ text: action.text, completed: false }])
case 'TOGGLE_TODO':
return state.map((todo, index) =>
action.index === index
? { text: todo.text, completed: !todo.completed }
: todo
)
default:
return state
}
}
再开发一个reducer 调用这两个 reducer,进而来管理整个应用得 state:
function todoApp(state = {}, action) {
return {
todos: todos(state.todos, action),
visibilityFilter: visibilityFilter(state.visibilityFilter, action)
}
}
这差不多就是 Redux 思想的全部。注意到没我们还没有使用任何 Redux 的 API。Redux 里有一些工具来简化这种模式,但是主要的想法是如何根据这些 action 对象来更新 state,而且 90% 的代码都是纯 JavaScript,没用 Redux、Redux API 和其它魔法。
Redux 的核心组件如下图:
三大原则
Redux 可以用这三个基本原则来描述:
单一数据源
整个应用得 state 被储存在一颗 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
这让同构应用开发变得非常容易。来自服务端的 state 可以在无需编写更多代码的情况下被序列化并注入到客户端中。由于是单一的 state tree ,调试也变得非常容易。在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。
State 是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心竞态条件(race condition)的出现。 Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
使用纯函数来进行修改
为了描述 action 如何改变 state tree,你需要编写 reducer。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。因为使用的是纯函数,相同的输入只会产生相同的输出,避免了产生不确定的结果,方便管理和使用。