我正在用 React 实现一个可过滤的列表。列表的结构如下图所示。
前提
以下是它应该如何工作的描述:
- 状态驻留在最高级别的
Search组件中。 - 状态描述如下:
 
{
    可见:布尔值,
    文件:数组,
    过滤:数组,
    请求参数,
    currentSelectedIndex : 整数
}
files是一个可能非常大的包含文件路径的数组(10000 个条目是一个合理的数字)。filtered是用户键入至少 2 个字符后过滤后的数组。我知道它是派生数据,因此可以就将其存储在状态中提出这样的论点,但它是必需的currentlySelectedIndex这是过滤列表中当前选定元素的索引。用户在
Input组件中输入2 个以上的字母,数组被过滤,对于过滤数组中的每个条目,一个Result组件被呈现每个
Result组件都显示与查询部分匹配的完整路径,并突出显示路径的部分匹配部分。例如,一个 Result 组件的 DOM,如果用户输入了 'le' 将会是这样的:<li>this/is/a/fi<strong>le</strong>/path</li>- 如果用户在
Input组件聚焦时按下向上或向下键,则currentlySelectedIndex基于filtered数组的更改。这会导致Result与索引匹配的组件被标记为选中,从而导致重新渲染 
问题
最初,我files使用 React 的开发版本,使用足够小的 数组对此进行了测试,并且一切正常。
当我不得不处理一个files有 10000 个条目的数组时,问题就出现了。在 Input 中输入 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
我找到了迈克尔杰克逊的这个演讲,他在那里解决了一个与这个问题非常相似的问题:https : //youtu.be/7S8v8jfLb1Q?t=26m2s
该解决方案与下面AskarovBeknar 的回答提出的解决方案非常相似
更新 14-4-2018
由于这显然是一个受欢迎的问题,并且自从提出原始问题以来事情已经取得了进展,虽然我鼓励您观看上面链接的视频,为了掌握虚拟布局,我也鼓励您使用React Virtualized图书馆,如果你不想重新发明轮子。



