Я работаю над реализацией фильтруемого списка с помощью 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 библиотека, если вы не хотите заново изобретать колесо.