Я нахожусь в процессе реализации фильтруемого списка с React. Структура списка показана на рисунке ниже.
ПОМЕЩЕНИЕ
Вот описание того, как это должно работать:
- Состояние находится в компоненте самого высокого уровня, компоненте
Search
. - Состояние описывается следующим образом:
{ visible : boolean, files : array, filtered : array, query : string, currentlySelectedIndex : integer }
files
- потенциально очень большой массив, содержащий пути к файлам (10000 записей - вероятное число).filtered
- фильтруемый массив после того, как пользователь вводит не менее 2 символов. Я знаю, что это производные данные, и поэтому можно привести аргумент о том, что они должны храниться в состоянии, но это необходимо дляcurrentlySelectedIndex
, который является индексом выбранного в данный момент элемента из отфильтрованного списка.Пользователь вводит более 2 букв в компонент
Input
, массив фильтруется, и для каждой записи в фильтрованном массиве отображается компонентResult
Каждый компонент
Result
отображает полный путь, который частично соответствует запросу, и часть пути с частичным совпадением выделяется. Например, DOM компонента Result, если бы пользователь ввел 'le', был бы примерно таким:<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, все работало гладко, без задержек.
BottomLine
Является ли такое заметное несоответствие между разработкой и производственной версией React нормальным?
Я понимаю/делаю что-то не так, когда думаю о том, как React управляет списком?
ОБНОВЛЕНИЕ 14-11-2016
Я нашел эту презентацию Майкла Джексона, где он решает проблему, очень похожую на эту: https://youtu.be/7S8v8jfLb1Q?t=26m2s
Решение очень похоже на предложенное AskarovBeknar answer, ниже
ОБНОВЛЕНИЕ 14-4-2018
Поскольку этот вопрос, по-видимому, является популярным, и с тех пор, как был задан первоначальный вопрос, дела пошли успешно, хотя я призываю вас посмотреть видео, указанное выше, чтобы понять виртуальный макет, я также призываю вас использовать Реагируйте на виртуализированную библиотеку, если вы не хотите заново изобретать колесо.