Skip to main content

Redux 常见问答:React Redux

目录

React Redux

为什么应该使用 React-Redux?

Redux 本身是一个独立的库,可以与任何 UI 层或框架一起使用,包括 React、Angular、Vue、Ember 和 vanilla JS。虽然 Redux 和 React 通常一起使用,但它们是相互独立的。

Redux 与任何类型的 UI 框架一起使用时,通常会使用 “UI 绑定”库将 Redux 与 UI 框架绑定在一起,而不是直接在 UI 代码里与 store 进行交互。

React-Redux 是 React 的官方 Redux UI 绑定库。如果想要同时使用 Redux 和 React,你也应该使用 React-Redux 来绑定这两个库。

虽然可以手动编写 Redux store 的订阅逻辑,但这样做会有很多重复工作。此外,优化 UI 性能包含复杂的逻辑。

订阅 store、检查更新数据和触发重新渲染的过程可以变得更加通用和可复用。像 React-Redux 这样的 UI 绑定库会处理 store 的交互逻辑,因此你不必手动编写该代码。

总体而言,React-Redux 鼓励良好的 React 架构,并为你实现复杂的性能优化。它还与来自 Redux 和 React 的最新 API 更改保持同步。

更多信息

文档

为什么我的组件没有重新渲染,或者 mapStateToProps 没有运行?

到目前为止,意外地直接改变或修改 state 是组件在 dispatch action 后不重新渲染的最常见原因。Redux 期望 reducer “不可变地”更新 state,这实际上意味着始终制作数据副本,并将更改应用于副本。如果你从 reducer 返回相同的对象,Redux 会假设没有任何改变,即使你改变了它的内容。类似地,React Redux 尝试通过对 shouldComponentUpdate 中的传入 props 进行浅对比检查来提高性能,如果所有引用都相同,则 shouldComponentUpdate 返回 false 以跳过实际更新原始组件的步骤。

重点要记住,每当你更新嵌套值时,还必须在 state 树中返回它所有的上层内容的新副本。比如你有 state.a.b.c.d,并且想要更新 d,还需要返回 cbastate 的新副本。这个 state 树突变图演示了树深处的变化如何需要一直向上变化。

请注意,“不可变地更新数据” 并不 意味着你必须使用 Immer,尽管这当然是一种选择。你可以使用几种不同的方法对普通 JS 对象和数组进行不可变更新:

  • 使用 Object.assign()_.extend() 等函数以及 slice()concat() 等数组函数来复制对象
  • ES6 中的数组扩展运算符,以及未来版本的 JavaScript 提出的对象扩展运算符
  • 将不可变更新逻辑包装成更简单函数的实用程序库

更多信息

文档

文献

讨论

为什么我的组件经常重新渲染?

React Redux 实现了多项优化,以确保组件仅在实际需要时重新渲染。其中之一是对传递给 connectmapStateToPropsmapDispatchToProps 参数生成的组合 props 对象进行浅层相等检查。不幸的是,在每次调用 mapStateToProps 时都会创建新的数组或对象实例的情况下,浅层相等检查是没有用的。一个典型的例子是映射一个 ID 数组并返回匹配的对象引用,例如:

const mapStateToProps = state => {
return {
objects: state.objectIds.map(id => state.objects[id])
}
}

虽然数组可能每次都包含完全相同的对象引用,但数组本身是不同的引用,因此浅层相等检查失败,React Redux 将重新渲染包装的组件。

额外的重新渲染可以通过使用 reducer 将对象数组保存到 state 中、使用 Reselect 缓存映射数组或手动在组件中实现 shouldComponentUpdate,并使用诸如 _.isEqual 之类的函数进行更深入的 props 比较。注意不要让你的自定义 shouldComponentUpdate() 比渲染本身代价更大!始终使用分析器来检查你对性能的预期。

对于未连接的组件,你可能需要检查传入的 props 的内容。一个常见的问题是父组件在其渲染函数中重新绑定回调,例如 <Child onClick={this.handleClick.bind(this)} />。每次父级重新渲染时,都会创建一个新的函数引用。更好的做法是仅在父组件的构造函数中绑定一次回调。

更多信息

文档

文献

讨论

相关库

如何加速 mapStateToProps

虽然 React Redux 确实可以最大限度地减少调用 mapStateToProps 函数的次数,但确保 mapStateToProps 快速运行并最大限度地减少它所做的工作量仍然是一个很好的做法。常用的推荐方法是使用 Reselect 创建记忆化的 “selector” 函数。这些 selectors 可以组合在一起,并且后续在管道中的 selectors 只有在它们的输入发生变化时才会运行。这意味着你可以创建执行过滤或排序等操作的 selectors,并确保它们仅在需要时才会实际工作。

更多信息

文档

文献

讨论

为什么 connected 组件中没有 this.props.dispatch 可用?

connect() 函数有两个主要参数,都是可选的。第一个参数,mapStateToProps,是你提供的一个函数,用于在 store 发生变化时从 store 中提取数据,并将这些值作为 props 传递给你的组件。第二个参数,mapDispatchToProps,也是你提供的一个函数,用于使用 store 的 dispatch 函数,通常通过创建 action creators 的预绑定版本,一旦它们被调用就会自动 dispatch actions。

如果你在调用 connect() 时没有提供 mapDispatchToProps 函数,React Redux 将提供一个默认版本,它只是简单地返回 dispatch 函数作为 props。这意味着如果你 确实 提供了你自己的函数,dispatch 就不会 自动提供。如果你仍然希望它作为 prop 可用,则需要在 mapDispatchToProps 函数实现中显式返回它。

更多信息

文档

讨论

应该只 connect 顶层组件,还是可以 connect 树中的多个组件?

早期的 Redux 文档建议在组件树的顶层附近只应该有几个 connected 的组件。但是,时间和经验表明,这样的组件架构一般需要少数几个组件额外地了解其所有后代的数据需求,并迫使它们传递大量令人困惑的 props。

当前建议的最佳实践是将你的组件分类为 “UI 组件”或“容器组件”,并在任何有意义的地方提取 connected 的容器组件:

在 Redux 示例中强调“一个容器组件在顶层”是错误的做法。但不要把这当作格言。尝试将你的 UI 组件分开。在适宜的时机通过 connect 它们来创建容器组件。每当你觉得需要在父组件中复制代码来为相同类型的子组件提供数据时,此时就是提取一个容器的好时机。通常,一旦你觉得“父母”对“个体”数据或其子女的行为了解太多的时候,就该提取容器了。

事实上,基准测试表明,更多的 connect 组件通常比更少的 connect 组件带来更好的性能。

总而言之,尝试在可理解的数据流和组件的责任范围之间找到平衡。

更多信息

文档

文献

讨论

Redux 与 React Context API 相比如何?

相似之处

Redux 以及 React 的 Context API 都用于处理 “prop 透传”。也就是说,它们都允许你传递数据,而无需通过多层组件传递 props。Redux 内部 使用 React 的 context API,以允许它沿着组件树传递 store。

差异

使用 Redux,你可以获得 Redux Dev Tools 扩展的强大功能。它会自动记录应用程序执行的每个 action,并支持时间旅行——你可以单击任何过去的 action 并跳转到该时间点。Redux 还支持 middleware 的概念,你可以在每个 action dispatch 上绑定自定义函数调用。此类示例包括自动事件记录器、拦截某些 actions 等。

使用 React 的 Context API,你可以处理一对只相互通信的组件。这样可以很好地隔离不相关的数据。你还可以灵活地使用组件中的数据,即,你可以提供父组件的 state,并且可以将上下文数据作为 props 传递给包装的组件。

Redux 和 React 的 Context 处理数据的方式有一个关键的区别。Redux 将整个应用程序的数据保存在一个巨大的、有状态的对象中。它通过运行你提供的 reducer 函数来推断数据的变化,并返回与 dispatch 的每个 action 相对应的下一个 state。React Redux 接着优化组件渲染,并确保每个组件仅在其需要的数据发生变化时重新渲染。另一方面,context 不包含任何 state,它只是数据的管道。要体现数据的变化,你需要依赖父组件的 state。

更多信息