ReactJS Reselect容器重复渲染问题求助
嘿,我之前也踩过这个坑,给你详细说说怎么解决!你遇到的Connect(Single Entries Index)组件明明只有1个实例却重复渲染的问题,核心原因出在Reselect默认的缓存比较逻辑上。
先看看你的原代码:
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { shape } from 'prop-types'; import Entry from '/components/entries/Entry'; class Entries extends Component { static displayName = 'Single Entries Index'; static propTypes = { entries: shape() }; shouldComponentUpdate(nextProps) { if (this.props.entries.size !== nextProps.entries.size) { return true; } return false; } render() { const { entries } = this.props; return ( <ul className="entries"> {entries.map(entry => ( <Entry key={entry.get('id')} id={entry.get('id')} /> ))} </ul> ); } }; const makeGetEntries = createSelector( state => state.data, data => data.get('entries') ); const mapStateToProps = (state) => { return { entries: makeGetEntries(state) }; }; export default connect(mapStateToProps)(Entries);
问题分析
Reselect默认的createSelector使用**浅比较(===)**来判断是否需要更新缓存。你这里返回的data.get('entries')看起来是Immutable.js的集合(从get、size方法能看出来),当Redux store有其他无关更新时,哪怕entries本身的内容没变化,state.data的引用可能发生了改变,导致默认selector认为依赖项更新了,返回一个新的entries引用。
而你的shouldComponentUpdate只判断了entries.size,但如果entries内容变化但size不变会漏判,不过当前更紧急的是:每次selector返回新引用时,哪怕内容完全一样,connect都会触发组件重新渲染——这就是Perf工具显示"重复渲染"的原因。
解决方案:自定义深比较Selector
@Logar给出的方案非常靠谱,通过createSelectorCreator替换默认的比较逻辑,改用深比较来判断缓存是否有效。这样只要entries的内容没变化,selector就会返回缓存的旧引用,不会触发不必要的渲染。
修改后的代码:
import { createSelectorCreator, defaultMemoize } from 'reselect'; import isEqual from 'lodash/isEqual'; // 或者你用的其他深比较工具 const createDeepEqualSelector = createSelectorCreator( defaultMemoize, isEqual ); const makeGetEntries = createDeepEqualSelector( state => state.data, data => data.get('entries') ); const mapStateToProps = (state) => { return { entries: makeGetEntries(state) }; }; export default connect(mapStateToProps)(Entries);
额外优化:适配Immutable.js的比较
如果你用的是Immutable.js,更推荐用它自带的is方法来做比较,比lodash的isEqual更高效,也更贴合Immutable的数据结构:
import { createSelectorCreator, defaultMemoize } from 'reselect'; import { is } from 'immutable'; const createImmutableSelector = createSelectorCreator( defaultMemoize, is ); const makeGetEntries = createImmutableSelector( state => state.data, data => data.get('entries') );
这样调整后,再用Perf.printWasted检查,应该就不会看到那个不必要的重复渲染了!
内容的提问来源于stack exchange,提问作者Jhay

