跳至主要内容

Redux 常见问题解答:React Redux

目录

React Redux

为什么我应该使用 React-Redux?

Redux 本身是一个独立的库,可以与任何 UI 层或框架一起使用,包括 React、Angular、Vue、Ember 和原生 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 没有运行?

直接修改或变异状态是最常见的导致组件在分发动作后不重新渲染的原因。Redux 期待你的 reducer 以“不可变”的方式更新状态,这意味着始终创建数据的副本,并在副本上应用更改。如果你从 reducer 返回相同的对象,Redux 会认为没有发生任何更改,即使你对它的内容进行了更改。类似地,React Redux 试图通过对传入 props 进行浅层等效引用检查来提高性能,如果所有引用都相同,shouldComponentUpdate 会返回 false 以跳过实际更新你的原始组件。

重要的是要记住,无论何时更新嵌套值,你都必须返回状态树中所有高于它的新副本。如果你有 state.a.b.c.d,并且想要更新 d,你还需要返回 cbastate 的新副本。这 状态树变异图 展示了树中深层的更改如何需要一直向上进行更改。

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

  • 使用 Object.assign()_.extend() 等函数复制对象,以及使用 slice()concat() 等数组函数
  • ES2015 中的数组扩展运算符,以及 ES2018 中类似的对象扩展运算符
  • 将不可变更新逻辑封装到更简单函数中的实用程序库

更多信息

文档

文章

讨论

为什么我的组件渲染次数过多?

React Redux 实现了多个优化措施,以确保您的实际组件仅在必要时重新渲染。其中一项优化是针对由传递给 connectmapStateToPropsmapDispatchToProps 参数生成的组合 props 对象进行浅层等式检查。不幸的是,浅层等式在每次调用 mapStateToProps 时创建新的数组或对象实例的情况下无济于事。一个典型的例子可能是遍历一个 ID 数组并返回匹配的对象引用,例如

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

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

可以通过将对象数组保存到状态中(使用 reducer)、使用 Reselect 缓存映射后的数组,或在组件中手动实现 shouldComponentUpdate 并使用 _.isEqual 等函数进行更深入的 props 比较来解决额外的重新渲染问题。注意不要让您的自定义 shouldComponentUpdate() 比渲染本身更昂贵!始终使用分析器来检查您对性能的假设。

对于非连接组件,您可能需要检查正在传递的 props。一个常见的问题是父组件在其渲染函数中重新绑定回调,例如 <Child onClick={this.handleClick.bind(this)} />。这会在父组件每次重新渲染时创建一个新的函数引用。通常,最好只在父组件的构造函数中绑定回调一次。

更多信息

文档

文章

讨论

如何加快我的 mapStateToProps

虽然 React Redux 确实努力最大限度地减少 mapStateToProps 函数的调用次数,但确保 mapStateToProps 运行速度快并最大限度地减少其工作量仍然是一个好主意。常见的推荐方法是使用 Reselect 创建记忆化的“选择器”函数。这些选择器可以组合和构成在一起,管道中后面的选择器只有在它们的输入发生变化时才会运行。这意味着您可以创建执行诸如过滤或排序之类的操作的选择器,并确保实际工作仅在需要时才进行。

更多信息

文档

文章

讨论

为什么我在连接的组件中没有 this.props.dispatch

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

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

更多信息

文档

讨论

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

早期的 Redux 文档建议你应该只在组件树的顶部有几个连接的组件。然而,时间和经验表明,这种组件架构通常需要一些组件了解所有后代的数据需求,并迫使它们传递大量令人困惑的 props。

目前建议的最佳实践是将你的组件分类为“展示”或“容器”组件,并在有意义的地方提取一个连接的容器组件

在 Redux 示例中强调“顶部的一个容器组件”是一个错误。不要把它当作格言。尝试将你的展示组件分开。在方便的时候通过连接它们来创建容器组件。每当你觉得你在父组件中重复代码以提供相同类型子组件的数据时,就该提取一个容器了。通常,当你觉得父组件了解太多关于其子组件的“个人”数据或操作时,就该提取一个容器了。

事实上,基准测试表明,连接的组件越多,通常比连接的组件越少,性能越好。

一般来说,尝试在组件的可理解数据流和职责范围之间找到平衡。

更多信息

文档

文章

讨论

Redux 与 React Context API 相比如何?

相似之处

Redux 和 React 的 Context API 都处理“道具钻取”。也就是说,它们都允许您传递数据,而无需通过多层组件传递道具。在内部,Redux *使用* React Context API,它允许它将存储传递到您的组件树中。

差异

使用 Redux,您可以获得 Redux Dev Tools Extension 的强大功能。它会自动记录您的应用程序执行的每个操作,并允许时间旅行 - 您可以点击任何过去的动作并跳转到那个时间点。Redux 还支持中间件的概念,您可以在其中将自定义函数调用绑定到每个操作调度。此类示例包括自动事件记录器、拦截某些操作等。

使用 React 的 Context API,您处理的是一对组件,它们仅相互通信。这为您提供了无关数据的良好隔离。您还可以灵活地选择如何在组件中使用数据,即您可以提供父组件的状态,并且可以将上下文数据作为道具传递给包装的组件。

Redux 和 React 的 Context 处理数据的方式存在关键差异。Redux 将整个应用程序的数据维护在一个巨大的有状态对象中。它通过运行您提供的 reducer 函数来推断数据的更改,并返回与每个调度操作相对应的下一个状态。React Redux 然后优化组件渲染,并确保每个组件仅在它需要的数据发生更改时重新渲染。另一方面,Context 不保存任何状态。它只是一个数据的管道。要表达数据中的更改,您需要依赖父组件的状态。

更多信息