我正在用React实现一个可过滤列表。列表的结构如下图所示。
前提
这是它应该如何工作的描述:
- 状态位于最高级别的
Search
组件中。 - 状态描述如下:
{ 可见:布尔值, 文件:数组, 过滤:数组, 请求参数, currentSelectedIndex:整数 }
files
是一个可能非常大的包含文件路径的数组(10000个条目是一个合理的数字)。filtered
是用户键入至少2个字符后的过滤数组。我知道它是派生数据,因此可以就将其存储在状态中进行论证,但对于currentlySelectedIndex
这是过滤列表中当前选定元素的索引。User types more than 2 letters into the
Input
component, the array is filtered and for each entry in the filtered array aResult
component is renderedEach
Result
component is displaying the full path that partially matched the query, and the partial match part of the path is highlighted. For example the DOM of a Result component, if the user had typed 'le' would be something like this :<li>this/is/a/fi<strong>le</strong>/path</li>
- If the user presses the up or down keys while the
Input
component is focused thecurrentlySelectedIndex
changes based on thefiltered
array. This causes theResult
component that matches the index to be marked as selected causing a re-render
PROBLEM
Initially I tested this with a small enough array of files
, using the development version of React, and all worked fine.
The problem appeared when I had to deal with a files
array as big as 10000 entries. Typing 2 letters in the Input would generate a big list and when I pressed the up and down keys to navigate it it would be very laggy.
At first I did not have a defined component for the Result
elements and I was merely making the list on the fly, on each render of the Search
component, as such:
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));
As you can tell, every time the currentlySelectedIndex
changed, it would cause a re-render and the list would be re-created each time. I thought that since I had set a key
value on each li
element React would avoid re-rendering every other li
element that did not have its className
change, but apparently it wasn't so.
I ended up defining a class for the Result
elements, where it explicitly checks whether each Result
element should re-render based on whether it was previously selected and based on the current user input :
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>
);
}
});
And the list is now created as such:
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));
}
This made performance slightly better, but it's still not good enough. Thing is when I tested on the production version of React things worked buttery smooth, no lag at all.
BOTTOMLINE
Is such a noticeable discrepancy between development and production versions of React normal?
Am I understanding/doing something wrong when I think about how React manages the list?
UPDATE 14-11-2016
I have found this presentation of Michael Jackson, where he tackles an issue very similar to this one: https://youtu.be/7S8v8jfLb1Q?t=26m2s
The solution is very similar to the one proposed by AskarovBeknar's answer, below
UPDATE 14-4-2018
Since this is apparently a popular question and things have progressed since the original question was asked, while I do encourage you to watch the video linked above, in order to get a grasp of a virtual layout, I also encourage you to use the React Virtualized library if you do not want to re-invent the wheel.
在加载到React组件之前尝试进行过滤,并且仅在组件中显示合理数量的项目,然后根据需要加载更多内容。没有人可以一次查看很多项目。
我认为您不是,但不要使用索引作为键。
要找出开发版本和生产版本不同的真正原因,可以尝试使用
profiling
代码。加载页面,开始记录,进行更改,停止记录,然后检查时间。有关在Chrome中进行性能分析的说明,请参见此处。