Skip to main content

React Redux

Redux 常见问题:React Redux

为什么我要使用 React-Redux?

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

如果你在任何 UI 框架中使用 Redux,通常会使用一个“UI 绑定”库,将 Redux 和你的 UI 框架连接起来,而不是直接从你的 UI 代码中操作 store。

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

虽然也可以手写 Redux store 的订阅逻辑,但这会非常重复。此外,要优化 UI 性能还需要复杂的逻辑。

订阅 store、检查数据更新并触发重新渲染的过程可以变得更通用且可复用。像 React-Redux 这样的 UI 绑定库处理 store 交互逻辑,因此你无需自己写这部分代码。

总体来说,React-Redux 鼓励良好的 React 架构,并帮你实现复杂的性能优化。同时它会及时更新,支持 Redux 和 React 的最新 API 变化。

详细信息

文档

为什么我的组件没有重新渲染,或者我的 mapStateToProps 没有被调用?

最常见导致组件在派发 action 后不重新渲染的原因是意外地直接修改了你的 state。Redux 期望你的 reducer 以“不变式”的方式更新 state,也就是说,总是复制数据,基于副本来做修改。如果 reducer 返回了相同的对象引用,即使内部内容改变了,Redux 也认为没有改变。同样,React Redux 为了优化性能,会在 shouldComponentUpdate 中做浅层相等性引用检查,如果所有引用都相同,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 的 state,或使用 Reselect 缓存映射数组,或手写 shouldComponentUpdate 并用类似 _.isEqual 函数做深入比较来解决额外的重渲染问题。注意不要让自定义的 shouldComponentUpdate() 比渲染本身还耗性能!一定用性能分析工具验证你的性能假设。

对于未连接的组件,请检查传入的 props。一个常见问题是父组件在 render 中重新绑定回调函数,如 <Child onClick={this.handleClick.bind(this)} />,这会每次父组件渲染都创建新的函数引用。通常建议只在父组件构造函数中绑定一次回调。

详细信息

文档

文章

讨论

如何加速我的 mapStateToProps

虽然 React Redux 会尽量减少调用 mapStateToProps 的次数,但确保 mapStateToProps 运行迅速且做的工作量最小依然很重要。常用做法是用 Reselect 创建记忆化的“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 都能解决“prop drilling”问题,即允许你不必经过多层组件传递 props。内部上,Redux 使用了 React 的 Context API 来将 store 传递给组件树。

区别

使用 Redux,你可以利用Redux Dev Tools 扩展,它会自动记录应用的每一个操作,支持时间旅行调试 —— 点击历史操作回到某个状态。Redux 还支持中间件概念,可以在每次 action 分发时绑定自定义函数,例如自动事件日志、拦截特定动作等。

React Context API 是一对组件内部通信机制,隔离无关数据,且允许灵活使用数据,比如可以提供父组件的 state,并可将 context 数据作为 props 传给包裹的组件。

Redux 和 React Context 在数据处理上有本质区别。Redux 把整个应用数据保存在庞大的有状态对象中,通过运行你提供的 reducer 推断数据变化,返回对应的下一状态。React Redux 再优化组件渲染,确保只有数据有变的组件重渲染。Context 本身不持有状态,只是数据的通道。你需要依赖父组件状态来表达数据变化。

详细信息