Я работаю над реализацией фильтруемого списка с помощью React. Структура списка показана на изображении ниже.
ПОМЕЩЕНИЕ
Вот описание того, как это должно работать:
- Состояние находится в компоненте самого высокого уровня,
Searchкомпоненте. - Состояние описывается следующим образом:
{
видимый: логический,
файлы: массив,
фильтруется: массив,
Строка запроса,
currentSelectedIndex: целое число
}
filesпотенциально очень большой массив, содержащий пути к файлам (10 000 записей - вероятное число).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, все работало плавно, без задержек.
НИЖНЯЯ ЛИНИЯ
Является ли такое заметное несоответствие между разрабатываемой и производственной версиями React нормальным?
Я понимаю / делаю что-то не так, когда думаю о том, как React управляет списком?
ОБНОВЛЕНИЕ 14-11-2016
Я нашел эту презентацию Майкла Джексона, в которой он решает проблему, очень похожую на эту: https://youtu.be/7S8v8jfLb1Q?t=26m2s
Решение очень похож на тот , предложенный AskarovBeknar в ответ ниже
ОБНОВЛЕНИЕ 14-4-2018
Поскольку это, по-видимому, популярный вопрос, и с тех пор, как был задан исходный вопрос, ситуация улучшилась, хотя я рекомендую вам посмотреть видео, указанное выше, чтобы получить представление о виртуальном макете, я также рекомендую вам использовать React Virtualized библиотека, если вы не хотите заново изобретать колесо.



