Redux 常见问题:React Redux

目录

React Redux

为何组件没有被重新渲染、或者 mapStateToProps 没有运行?

目前来看,导致组件在 action 分发后却没有被重新渲染,最常见的原因是对 state 进行了直接修改。Redux 期望 reducer 以 “不可变的方式” 更新 state,实际使用中则意味着复制数据,然后更新数据副本。如果直接返回同一对象,即使你改变了数据内容,Redux 也会认为没有变化。类似的,React Redux 会在 shouldComponentUpdate 中对新的 props 进行浅层的判等检查,以期提升性能。如果所有的引用都是相同的,则返回 false 从而跳过此次对组件的更新。

需要注意的是,不管何时更新了一个嵌套的值,都必须同时返回上层的任何数据副本给 state 树。如果数据是 state.a.b.c.d,你想更新 d,你也必须返回 cba 以及 state 的拷贝。state 树变化图 展示了树的深层变化为何需要改变途经的结点。

“以不可变的方式更新数据” 并 代表你必须使用 Immutable.js, 虽然是很好的选择。你可以使用多种方法,达到对普通 JS 对象进行不可变更新的目的:

  • 使用类似于 Object.assign() 或者 _.extend() 的方法复制对象, slice()concat() 方法复制数组。
  • ES6 数组的 spread sperator(展开运算符),JavaScript 新版本提案中类似的对象展开运算符。
  • 将不可变更新逻辑包装成简单方法的工具库。

补充资料

文档

文章

讨论

为何组件频繁的重新渲染?

React Redux 采取了很多的优化手段,保证组件直到必要时才执行重新渲染。一种是对 mapStateToPropsmapDispatchToProps 生成后传入 connect 的 props 对象进行浅层的判等检查。遗憾的是,如果当 mapStateToProps 调用时都生成新的数组或对象实例的话,此种情况下的浅层判等不会起任何作用。一个典型的示例就是通过 ID 数组返回映射的对象引用,如下所示:

  1. const mapStateToProps = (state) => {
  2. return {
  3. objects: state.objectIds.map(id => state.objects[id])
  4. }
  5. }

尽管每次数组内都包含了同样的对象引用,数组本身却指向不同的引用,所以浅层判等的检查结果会导致 React Redux 重新渲染包装的组件。

这种额外的重新渲染也可以避免,使用 reducer 将对象数组保存到 state,利用 Reselect 缓存映射的数组,或者在组件的 shouldComponentUpdate 方法中,采用 _.isEqual 等对 props 进行更深层次的比较。注意在自定义的 shouldComponentUpdate() 方法中不要采用了比重新渲染本身更为昂贵的实现。可以使用分析器评估方案的性能。

对于独立的组件,也许你想检查传入的 props。一个普遍存在的问题就是在 render 方法中绑定父组件的回调,比如 <Child onClick={this.handleClick.bind(this)} />。这样就会在每次父组件重新渲染时重新生成一个函数的引用。所以只在父组件的构造函数中绑定一次回调是更好的做法。

补充资料

文档

文章

讨论

怎样使 mapStateToProps 执行更快?

尽管 React Redux 已经优化并尽量减少对 mapStateToProps 的调用次数,加快 mapStateToProps 执行并减少其执行次数仍然是非常有价值的。普遍的推荐方式是利用 Reselect 创建可记忆(memoized)的 “selector” 方法。这样,selector 就能被组合在一起,并且同一管道(pipeline)后面的 selector 只有当输入变化时才会执行。意味着你可以像筛选器或过滤器那样创建 selector,并确保任务的执行时机。

补充资料

文档

文章

讨论

为何不在被连接的组件中使用 this.props.dispatch

connect() 方法有两个主要的参数,而且都是可选的。第一个参数 mapStateToProps 是个函数,让你在数据变化时从 store 获取数据,并作为 props 传到组件中。第二个参数 mapDispatchToProps 依然是函数,让你可以使用 store 的 dispatch 方法,通常都是创建 action 创建函数并预先绑定,那么在调用时就能直接分发 action。

如果在执行 connect() 时没有指定 mapDispatchToProps 方法,React Redux 默认将 dispatch 作为 prop 传入。所以当你指定方法时, dispatch 会自动注入。如果你还想让其作为 prop,需要在 mapDispatchToProps 实现的返回值中明确指出。

补充资料

文档

讨论

应该只连接到顶层组件吗,或者可以在组件树中连接到不同组件吗?

早期的 Redux 文档中建议只在组件树顶层附近连接若干组件。然而,时间和经验都表明,这需要让这些组件非常了解它们子孙组件的数据需求,还导致它们会向下传递一些令人困惑的 props。

目前的最佳实践是将组件按照 “展现层(presentational)” 或者 “容器(container)” 分类,并在合理的地方抽象出一个连接的容器组件:

Redux 示例中强调的 “在顶层保持一个容器组件” 是错误的。不要把这个当做准则。让你的展现层组件保持独立。然后创建容器组件并在合适时进行连接。当你感觉到你是在父组件里通过复制代码为某些子组件提供数据时,就是时候抽取出一个容器了。只要你认为父组件过多了解子组件的数据或者 action,就可以抽取容器。

总之,试着在数据流和组件职责间找到平衡。

补充资料

文档

文章

讨论