我正在使用 React 实现一个可过滤列表。列表的结构如下图所示。
前提
下面是它应该如何工作的描述:
Search
组件中。{ visible : boolean, files : array, filtered : array, query : string, currentlySelectedIndex : integer }
files
是一个潜在的非常大的数组,包含文件路径(10000个条目是一个合理的数字)。filtered
是用户键入至少2个字符后的筛选数组。我知道它是派生数据,因此可以在状态中存储它,但这是必要的currentlySelectedIndex
,它是筛选列表中当前选定元素的索引。
用户在 Input
组件中键入2个以上的字母,然后过滤数组,并为过滤数组中的每个条目呈现一个 Result
组件
每个 Result
组件都显示部分匹配查询的完整路径,并突出显示路径的部分匹配部分。例如,如果用户输入了“ le”,那么 Result 组件的 DOM 应该是这样的:
<li>this/is/a/fi<strong>le</strong>/path</li>
Input
组件的重点是基于 filtered
阵列的 currentlySelectedIndex
变化。这会导致与索引匹配的 Result
组件被标记为选定,从而导致重新呈现问题
最初,我用一个足够小的 files
数组(使用 React 的开发版本)对此进行了测试,结果一切正常。
当我不得不处理大至10000个条目的 files
数组时,问题出现了。在输入框中键入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
更改时,都会导致重新呈现,并且每次都会重新创建列表。我想既然我已经为每个 li
元素设置了一个 key
值,那么 React 就可以避免重新渲染其他没有 className
变化的 li
元素,但显然事实并非如此。
最后,我为 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 如何管理列表时,我是否理解/做错了什么
更新日期14-11-2016
我发现了迈克尔 · 杰克逊的这个演讲,他处理了一个与这个非常相似的问题: https://youtu.be/7S8v8jfLb1Q?t=26m2s
这个解决方案与 AskarovBeknar 的 回答提出的解决方案非常相似
更新14-4-2018
由于这显然是一个很受欢迎的问题,而且自从最初的问题被提出以来,事情已经有了进展,虽然我鼓励你观看上面链接的视频,为了掌握一个虚拟布局,我也鼓励你使用 反应虚拟化库,如果你不想重新发明轮子。