我正在用React实现一个可过滤列表。列表的结构如下图所示。
前提
这是它应该如何工作的描述:
- 状态位于最高级别的
Search
组件中。 - 状态描述如下:
{ 可见:布尔值, 文件:数组, 过滤:数组, 请求参数, currentSelectedIndex:整数 }
files
是一个可能非常大的包含文件路径的数组(10000个条目是一个合理的数字)。filtered
是用户键入至少2个字符后的过滤数组。我知道它是派生数据,因此可以就将其存储在状态中进行论证,但对于currentlySelectedIndex
这是过滤列表中当前选定元素的索引。用户在
Input
组件中输入两个以上的字母,对数组进行过滤,并为过滤后的数组中的每个条目Result
呈现一个组件每个
Result
组件都显示与查询部分匹配的完整路径,并突出显示路径的部分匹配部分。例如,如果用户输入了“ le”,那么Result组件的DOM将是这样的:<li>this/is/a/fi<strong>le</strong>/path</li>
- 如果用户在
Input
聚焦组件时按下向上或向下键,则currentlySelectedIndex
基于filtered
阵列的更改。这导致Result
与索引匹配的组件被标记为选中,从而导致重新渲染
问题
最初,我files
使用React的开发版本使用足够小的数组对它进行了测试,并且一切正常。
当我不得不处理files
多达10000个条目的数组时,出现了问题。在“输入”中键入2个字母会生成一个大列表,当我按向上和向下键进行导航时,它会很麻烦。
最初,我没有为Result
元素定义组件,而只是在Search
组件的每个渲染过程中即时创建列表,如下所示:
results = this.state.filtered.map(function(file, index) {
var start, end, matchIndex, match = this.state.query;
matchIndex = file.indexOf(match);
start = file.slice(0, matchIndex);
end = file.slice(matchIndex + match.length);
return (
<li onClick={this.handleListClick}
data-path={file}
className={(index === this.state.currentlySelected) ? "valid selected" : "valid"}
key={file} >
{start}
<span className="marked">{match}</span>
{end}
</li>
);
}.bind(this));
如您所知,每次currentlySelectedIndex
更改都会导致重新渲染,并且每次都会重新创建列表。我以为,因为我已经key
在每个li
元素上设置了一个值,所以React可以避免重新渲染li
没有className
更改的所有其他元素,但是显然并非如此。
我最终为Result
元素定义了一个类,在该类中,它Result
根据先前是否被选择以及根据当前用户输入来显式检查是否应重新渲染每个元素:
var ResultItem = React.createClass({
shouldComponentUpdate : function(nextProps) {
if (nextProps.match !== this.props.match) {
return true;
} else {
return (nextProps.selected !== this.props.selected);
}
},
render : function() {
return (
<li onClick={this.props.handleListClick}
data-path={this.props.file}
className={
(this.props.selected) ? "valid selected" : "valid"
}
key={this.props.file} >
{this.props.children}
</li>
);
}
});
现在,清单是这样创建的:
results = this.state.filtered.map(function(file, index) {
var start, end, matchIndex, match = this.state.query, selected;
matchIndex = file.indexOf(match);
start = file.slice(0, matchIndex);
end = file.slice(matchIndex + match.length);
selected = (index === this.state.currentlySelected) ? true : false
return (
<ResultItem handleClick={this.handleListClick}
data-path={file}
selected={selected}
key={file}
match={match} >
{start}
<span className="marked">{match}</span>
{end}
</ResultItem>
);
}.bind(this));
}
这使性能稍好一些,但还不够好。事情是当我在React的生产版本上进行测试时,一切顺利进行,完全没有滞后。
底线
React开发版本和生产版本之间的这种明显差异正常吗?
当我考虑React如何管理列表时,我是否理解/做错了什么?
更新14-11-2016
我发现了迈克尔·杰克逊(Michael Jackson)的演示文稿,他在其中解决了一个与此非常类似的问题:https : //youtu.be/7S8v8jfLb1Q?t=26m2s
该解决方案与下面的AskarovBeknar的答案提出的解决方案非常相似。
更新14-4-2018
由于这显然是一个受欢迎的问题,并且自提出原始问题以来情况已经有所改善,尽管我确实鼓励您观看上面链接的视频,以便掌握虚拟布局,但我也鼓励您使用React Virtualized库,如果您不想重新发明轮子。