跳至主内容

React Redux

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

Redux 常见问题:React Redux

为什么应该使用 React-Redux?

Redux 本身是一个独立库,可与任何 UI 层或框架配合使用,包括 React、Angular、Vue、Ember 和原生 JS。尽管 Redux 和 React 常被搭配使用,但两者彼此独立。

当您在任意 UI 框架中使用 Redux 时,通常应使用 "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 未执行?

意外直接修改状态是触发 action 后组件未重新渲染的最常见原因。Redux 要求 reducer 以"不可变"方式更新状态,即始终创建数据副本并在副本上应用变更。若从 reducer 返回相同对象,即使内容已修改,Redux 也会认为无变化。同样地,React Redux 通过在 shouldComponentUpdate 中对传入 props 进行浅层引用检查来优化性能——若所有引用相同,shouldComponentUpdate 将返回 false 以跳过组件更新。

需特别注意:更新嵌套值时,必须同时返回状态树中其上层所有节点的新副本。若存在 state.a.b.c.d 并要更新 d,则需返回 cbastate 的新副本。此状态树变更示意图展示了深层变更如何引发整条路径的更新。

请注意"不可变更新数据" 意味着必须使用 Immer(尽管这是可选方案)。您可通过多种方式对普通 JS 对象和数组进行不可变更:

  • 使用 Object.assign()_.extend() 复制对象,以及 slice()concat() 等数组方法

  • ES2015 的数组展开运算符和 ES2018 的对象展开运算符

  • 封装不可变更新逻辑的实用工具库

扩展阅读

文档

文章

讨论

为什么我的组件过于频繁地重新渲染?

React Redux 实现了多种优化机制,确保组件仅在必要时重新渲染。其中一项优化是对 connect 函数中 mapStateToPropsmapDispatchToProps 生成的组合 props 对象进行浅层相等性检查。但若每次调用 mapStateToProps 时都创建新的数组或对象实例,浅层比较就会失效。典型场景如映射 ID 数组并返回对应对象引用:

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

即使每次数组包含相同的对象引用,数组本身的引用不同,浅层比较仍会失败,导致 React Redux 重新渲染包裹的组件。

可通过以下方式解决过度渲染问题:使用 reducer 将对象数组保存至状态、通过 Reselect 缓存映射结果,或在组件中手动实现 shouldComponentUpdate 并进行深度 props 比较(如使用 _.isEqual)。注意避免自定义 shouldComponentUpdate() 的开销超过渲染本身!务必使用性能分析工具验证优化效果。

对于非连接组件,需检查传入的 props。常见问题是父组件在渲染函数内重新绑定回调,如 <Child onClick={this.handleClick.bind(this)} />,这会导致每次父组件渲染时都创建新函数引用。最佳实践是在父组件构造函数中一次性绑定回调。

扩展阅读

文档

文章

讨论

相关库

如何加速 mapStateToProps 的执行?

尽管 React Redux 会尽量减少调用 mapStateToProps 函数的次数,但确保你的 mapStateToProps 快速运行并减少其工作量仍是明智之举。通常推荐的做法是使用 Reselect 创建记忆化(memoized)的 "selector" 函数。这些选择器可以相互组合,管道中后续的选择器只会在输入发生变化时执行。这意味着你可以创建用于过滤或排序的选择器,并确保仅在必要时才执行实际工作。

扩展阅读

文档

文章

讨论

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

connect() 函数接收两个主要参数(均为可选)。第一个参数 mapStateToProps 用于在 store 更新时提取数据,并将这些值作为 props 传递给组件。第二个参数 mapDispatchToProps 用于利用 store 的 dispatch 功能,通常通过创建预绑定的 action 创建器实现,这些创建器在被调用时会自动分发 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 来沿组件树传递 store。

区别

使用 Redux 时,你可以利用 Redux DevTools 扩展的强大功能。它会自动记录应用的每个操作,并支持时间旅行——你可以点击任何历史操作跳转到对应时间点。Redux 还支持中间件概念,允许在每次操作派发时绑定自定义函数调用,例如自动事件记录器或特定操作拦截器。

使用 React Context API 时,你处理的是成对通信的组件。这为无关数据提供了良好的隔离性。同时你在数据使用上更灵活:可以传递父组件的状态,也能将上下文数据作为属性传递给包裹的组件。

Redux 和 React Context 处理数据的核心区别在于:Redux 将整个应用数据维护在单一状态对象中,通过你提供的 reducer 函数推导数据变更,并为每个派发的操作返回新状态。React Redux 会优化组件渲染,确保组件仅在所需数据变化时重渲染。而 Context 本身不持有状态,仅是数据管道,数据变更需要依赖父组件的状态来实现。

扩展阅读