我们使用 Redux 来管理应用程序状态。 文档 对概念进行了很好的解释,所以请阅读它们。
快速入门¶
就像 React 简介 一样,这是一个 Redux 的快速介绍,重点介绍它如何融入 React 以及我们选择它的原因。
React 未解决的核心问题之一是管理状态。在 React 简介中,我们讨论了数据向下流动和事件向上流动。从概念上讲,这很好,但在大规模应用中,你很快就会遇到尴尬的情况。
让我们来看一个例子。假设你有一个带有选项卡界面的页面。这里,Tab1
正在管理项目列表,因此它自然会使用本地状态。Tab2
渲染不同的内容。
const Tab1 = React.createClass({
getInitialState: function() {
return { items: [] };
},
handleAddItem: function(item) {
this.setState({ items: [...this.state.items, item]});
},
render: function() {
/* ... Renders the items and button to add new item ... */
}
});
const Tab2 = React.createClass({
render: function() {
/* ... Renders other data ... */
}
});
// Assume `Tab1` and `Tab2` are wrapped with a factory when importing
const Tabs = React.createClass({
render: function() {
return div(
{ className: 'tabs' },
// ... Render the tab buttons ...
Tab1(),
Tab2()
);
}
});
但是,如果 Tab2
需要项目列表会发生什么?这种情况一直都会出现:不直接相关的组件需要访问相同的状态。一个小的更改是将 items
状态上移到 Tabs
组件,并将其传递给 Tab1
和 Tab2
。
但是现在 Tabs
必须实现 handleAddItem
方法来添加项目,因为它正在管理该状态。随着结果最终导致根组件拥有大量状态和方法来管理它,这种情况很快就会变得很糟糕:一个 上帝组件 诞生了。
此外,我们如何知道每个选项卡需要哪些数据?我们最终会将所有状态传递下去,因为我们不知道。这不是一个模块化的解决方案:一个对象管理状态,每个组件都接收整个状态,就像使用大量的全局变量一样。
Flux 的演变¶
Facebook 使用 flux 架构解决了这个问题,该架构将状态从组件中移出并放入“存储”中。Redux 是这个想法的最新演变,它解决了之前 Flux 库存在的大量问题(阅读其文档以获取更多信息)。
因为状态存在于组件树之外,所以任何组件都可以从中读取。此外,**状态使用 动作 更新**,任何组件都可以触发这些动作。我们有 指南 来指导在哪里读取/写入状态,但这完全解决了上面描述的问题。 Tab1
和 Tab2
都将侦听 item
状态的变化,而 Tab1
将触发动作来更改它。
使用 Redux,**状态使用 reducer 以模块化的方式管理**,但组合成一个单一的对象。这意味着单个 JS 对象表示你大部分*状态。起初这听起来可能很疯狂,但把它想象成一个包含对许多状态片段的引用的对象;它就是如此。
这使得测试、调试和通常的思考变得非常容易。你可以将你的整个状态记录到控制台并检查它。你甚至可以转储旧状态并“回放”以查看 UI 随时间推移的变化情况。
我说是“大部分*”,因为同时使用组件本地状态和 Redux 也是完全可以的。但请注意,任何调试工具都完全看不到本地状态。它应该仅用于瞬态状态;我们将在指南中详细讨论。
不变性¶
另一个重要的概念是不变性。在大规模应用中,更改状态会使跟踪何时发生更改变得非常困难。很容易遇到这种情况:某些内容在你不知情的情况下发生了更改,并且 UI 使用无效数据进行渲染。
Redux 强制状态以不可变的方式更新。这意味着你始终返回新的状态。这并不意味着每次都对状态进行深度复制:当你需要更改树的某些部分时,你只需要创建新的对象来替换你正在更改的对象(并向上走到根以创建一个新的根)。未更改的子树将引用相同的对象。
这消除了一整类错误,就像 Rust 通过强制所有权消除了一整类内存错误一样。
执行顺序¶
React 最好的方面之一是**渲染是同步的**。这意味着当你渲染一个组件时,给定一些数据,它将在同一 tick 中完全渲染。如果你希望 UI 随时间推移而更改,则必须更改数据并重新渲染,而不是进行任意的 UI 更改。
这样做的原因是,如果你围绕 Promise 或事件发射器构建 UI,则更新 UI 会变得非常脆弱,因为任何事情都可能在任何时间发生。状态可能会在渲染过程中被更新,可能是因为你解析了一些 Promise,这使得你的渲染代码在几个 tick 后才运行。
Redux 也采用了同步执行语义。这意味着所有事情都以一种非常受控的方式发生。当通过动作更新状态时,所有 reducer 都会运行,并且会同步生成一个新的状态。此时,新的状态将传递给 React 并同步渲染。
更新和渲染分两个阶段进行,因此 UI 将始终表示一致的状态。状态在渲染时永远不会处于更新的中间状态。
异步工作怎么办?这就是 中间件 的用武之地。此时你可能应该去研究我们的代码,但中间件允许你分派特殊的动作来指示异步工作。中间件将捕获这些动作并执行异步操作,在此过程中分派“原始”动作(通常会发出 START、DONE 和 ERROR 动作)。
**最终有 3 个“阶段”或抽象级别**:异步层与网络通信,并可能分派动作,动作同步地通过 reducer 传递以生成状态,状态使用 React 渲染。
下一步¶
接下来阅读 Redux 指南,以了解如何专门为 DevTools 编写 React 代码。