Я работаю над реализацией фильтруемого списка с помощью 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 библиотека, если вы не хотите заново изобретать колесо.
источник
Ответы:
Как и во многих других ответах на этот вопрос, основная проблема заключается в том, что рендеринг такого большого количества элементов в DOM при выполнении фильтрации и обработки ключевых событий будет медленным.
Вы не делаете ничего заведомо неправильного в отношении React, который вызывает проблему, но, как и многие другие проблемы, связанные с производительностью, пользовательский интерфейс также может принимать на себя большую часть вины.
Если ваш пользовательский интерфейс не ориентирован на эффективность, пострадают даже такие инструменты, как React, которые созданы для обеспечения высокой производительности.
Как отметил @Koen, фильтрация набора результатов - отличное начало
Я немного поэкспериментировал с этой идеей и создал пример приложения, иллюстрирующий, как я могу начать решать такую проблему.
Это ни в коем случае не
production ready
код, но он адекватно иллюстрирует концепцию и может быть изменен, чтобы сделать его более надежным, не стесняйтесь взглянуть на код - я надеюсь, что он, по крайней мере, дает вам некоторые идеи ...;)реагировать большой список пример
источник
127.0.0.1 * http://localhost:3001
?Мой опыт с очень похожей проблемой заключается в том, что реагирование действительно страдает, если в DOM одновременно присутствует более 100-200 или около того компонентов. Даже если вы будете очень осторожны (настроив все свои ключи и / или реализуя
shouldComponentUpdate
метод), чтобы изменить только один или два компонента при повторном рендеринге, вы все равно будете в мире боли.Медленная часть реакции на данный момент - это сравнение разницы между виртуальной и реальной DOM. Если у вас есть тысячи компонентов, но обновите только пару, это не имеет значения, у реакции все еще есть огромная разница в операции между DOM.
Когда я сейчас пишу страницы, я стараюсь спроектировать их так, чтобы минимизировать количество компонентов. Один из способов сделать это при визуализации больших списков компонентов - это ... ну ... не визуализировать большие списки компонентов.
Я имею в виду: визуализируйте только те компоненты, которые вы видите в настоящее время, визуализируйте больше при прокрутке вниз, вы вряд ли будете прокручивать тысячи компонентов в любом случае ... Я надеюсь.
Отличная библиотека для этого:
https://www.npmjs.com/package/react-infinite-scroll
Здесь есть отличные инструкции:
http://www.reactexamples.com/react-infinite-scroll/
Я боюсь, что он не удаляет компоненты, которые находятся вне верхней части страницы, поэтому, если вы прокручиваете достаточно долго, проблемы с производительностью начнут появляться снова.
Я знаю, что давать ссылку в качестве ответа - не лучшая практика, но примеры, которые они предоставляют, объяснят, как использовать эту библиотеку намного лучше, чем я могу здесь. Надеюсь, я объяснил, почему большие списки - это плохо, но это тоже обходной путь.
источник
Прежде всего, разница между разрабатываемой и производственной версией React огромна, потому что на производстве существует множество обходных проверок работоспособности (таких как проверка типов пропеллеров).
Затем, я думаю, вам следует пересмотреть использование Redux, потому что он был бы чрезвычайно полезен для того, что вам нужно (или для любой реализации потока). Вам следует окончательно взглянуть на эту презентацию: Big List High Performance React & Redux .
Но прежде чем погрузиться в redux, вам нужно внести некоторые коррективы в свой код React, разделив ваши компоненты на более мелкие компоненты, потому что
shouldComponentUpdate
это полностью обойдёт рендеринг дочерних элементов, так что это огромный выигрыш .Когда у вас есть более детализированные компоненты, вы можете обрабатывать состояние с помощью redux и react-redux, чтобы лучше организовать поток данных.
Недавно я столкнулся с аналогичной проблемой, когда мне нужно было отобразить тысячу строк и иметь возможность изменять каждую строку, редактируя ее содержимое. Это мини-приложение отображает список концертов с потенциальными дублирующими концертами, и мне нужно выбрать для каждого потенциального дубликата, если я хочу отметить потенциальный дубликат как оригинальный концерт (а не дубликат), установив флажок, и, при необходимости, отредактировать название концерта. Если я ничего не сделаю для конкретного потенциально повторяющегося элемента, он будет считаться дублирующимся и будет удален.
Вот как это выглядит:
В основном есть 4 сетевых компонента (здесь только одна строка, но это для примера):
Вот полный код (рабочий CodePen: огромный список с React и Redux ) с использованием redux , response-redux , immutable , повторного выбора и повторной компоновки :
const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ }) const types = { CONCERTS_DEDUP_NAME_CHANGED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED', CONCERTS_DEDUP_CONCERT_TOGGLED: 'diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED', }; const changeName = (pk, name) => ({ type: types.CONCERTS_DEDUP_NAME_CHANGED, pk, name }); const toggleConcert = (pk, toggled) => ({ type: types.CONCERTS_DEDUP_CONCERT_TOGGLED, pk, toggled }); const reducer = (state = initialState, action = {}) => { switch (action.type) { case types.CONCERTS_DEDUP_NAME_CHANGED: return state .updateIn(['names', String(action.pk)], () => action.name) .set('_state', 'not_saved'); case types.CONCERTS_DEDUP_CONCERT_TOGGLED: return state .updateIn(['concerts', String(action.pk)], () => action.toggled) .set('_state', 'not_saved'); default: return state; } }; /* configureStore */ const store = Redux.createStore( reducer, initialState ); /* SELECTORS */ const getDuplicatesGroups = (state) => state.get('duplicatesGroups'); const getDuplicateGroup = (state, name) => state.getIn(['duplicatesGroups', name]); const getConcerts = (state) => state.get('concerts'); const getNames = (state) => state.get('names'); const getConcertName = (state, pk) => getNames(state).get(String(pk)); const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk)); const getGroupNames = reselect.createSelector( getDuplicatesGroups, (duplicates) => duplicates.flip().toList() ); const makeGetConcertName = () => reselect.createSelector( getConcertName, (name) => name ); const makeIsConcertOriginal = () => reselect.createSelector( isConcertOriginal, (original) => original ); const makeGetDuplicateGroup = () => reselect.createSelector( getDuplicateGroup, (duplicates) => duplicates ); /* COMPONENTS */ const DuplicatessTableRow = Recompose.onlyUpdateForKeys(['name'])(({ name }) => { return ( <tr> <td>{name}</td> <DuplicatesRowColumn name={name}/> </tr> ) }); const PureToggle = Recompose.onlyUpdateForKeys(['toggled'])(({ toggled, ...otherProps }) => ( <input type="checkbox" defaultChecked={toggled} {...otherProps}/> )); /* CONTAINERS */ let DuplicatesTable = ({ groups }) => { return ( <div> <table className="pure-table pure-table-bordered"> <thead> <tr> <th>{'Concert'}</th> <th>{'Duplicates'}</th> </tr> </thead> <tbody> {groups.map(name => ( <DuplicatesTableRow key={name} name={name} /> ))} </tbody> </table> </div> ) }; DuplicatesTable.propTypes = { groups: React.PropTypes.instanceOf(Immutable.List), }; DuplicatesTable = ReactRedux.connect( (state) => ({ groups: getGroupNames(state), }) )(DuplicatesTable); let DuplicatesRowColumn = ({ duplicates }) => ( <td> <ul> {duplicates.map(d => ( <DuplicateItem key={d} pk={d}/> ))} </ul> </td> ); DuplicatessRowColumn.propTypes = { duplicates: React.PropTypes.arrayOf( React.PropTypes.string ) }; const makeMapStateToProps1 = (_, { name }) => { const getDuplicateGroup = makeGetDuplicateGroup(); return (state) => ({ duplicates: getDuplicateGroup(state, name) }); }; DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn); let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => { return ( <li> <table> <tbody> <tr> <td>{ toggled ? <input type="text" value={name} onChange={(e) => onNameChange(pk, e.target.value)}/> : name }</td> <td> <PureToggle toggled={toggled} onChange={(e) => onToggle(pk, e.target.checked)}/> </td> </tr> </tbody> </table> </li> ) } const makeMapStateToProps2 = (_, { pk }) => { const getConcertName = makeGetConcertName(); const isConcertOriginal = makeIsConcertOriginal(); return (state) => ({ name: getConcertName(state, pk), toggled: isConcertOriginal(state, pk) }); }; DuplicateItem = ReactRedux.connect( makeMapStateToProps2, (dispatch) => ({ onNameChange(pk, name) { dispatch(changeName(pk, name)); }, onToggle(pk, toggled) { dispatch(toggleConcert(pk, toggled)); } }) )(DuplicateItem); const App = () => ( <div style={{ maxWidth: '1200px', margin: 'auto' }}> <DuplicatesTable /> </div> ) ReactDOM.render( <ReactRedux.Provider store={store}> <App/> </ReactRedux.Provider>, document.getElementById('app') );
Уроки, извлеченные из этого мини-приложения при работе с огромным набором данных
connect
компонент ed для компонента, который является ближайшим к данным, которые им нужны, чтобы компонент не передавал только свойства, которые они не используютownProps
, необходимо, чтобы избежать бесполезного повторного рендерингаисточник
React в разрабатываемой версии проверяет типы свойств каждого компонента, чтобы упростить процесс разработки, в то время как в производственной версии он не используется.
Фильтрация списка строк - очень дорогая операция для каждого нажатия клавиши. это может вызвать проблемы с производительностью из-за однопоточной природы JavaScript. Решение может заключаться в использовании метода debounce, чтобы отложить выполнение вашей функции фильтра до истечения задержки.
Другой проблемой может быть сам огромный список. Вы можете создать виртуальный макет и повторно использовать созданные элементы, просто заменяя данные. По сути, вы создаете прокручиваемый контейнерный компонент с фиксированной высотой, внутри которого вы будете размещать контейнер списка. Высота контейнера списка должна быть установлена вручную (itemHeight * numberOfItems) в зависимости от длины видимого списка, чтобы полоса прокрутки работала. Затем создайте несколько компонентов элемента, чтобы они заполняли высоту прокручиваемых контейнеров и, возможно, добавили один или два дополнительных эффекта имитации непрерывного списка. сделайте их абсолютным положением, а при прокрутке просто переместите их положение, чтобы он имитировал непрерывный список (я думаю, вы узнаете, как это реализовать :)
Еще одна вещь: запись в DOM - тоже дорогостоящая операция, особенно если вы делаете это неправильно. Вы можете использовать холст для отображения списков и обеспечить плавность прокрутки. Оформить заказ на компоненты React-Canvas. Я слышал, что они уже поработали над списками.
источник
React in development
? а зачем проверять прототипы каждого компонента?Посмотрите React Virtualized Select, он разработан для решения этой проблемы и, по моему опыту, работает впечатляюще. Из описания:
https://github.com/bvaughn/react-virtualized-select
источник
Как я уже упоминал в своем комментарии , я сомневаюсь, что пользователям нужны все эти 10000 результатов в браузере сразу.
Что, если вы пролистываете результаты и всегда показываете список из 10 результатов.
Я создал пример используя эту технику, без использования какой-либо другой библиотеки, такой как Redux. В настоящее время только с клавиатурой навигации, но может быть легко расширен для работы с прокруткой.
Пример состоит из 3 компонентов: приложения-контейнера, компонента поиска и компонента списка. Практически вся логика перенесена в контейнерный компонент.
Суть заключается в отслеживании
start
и измененииselected
результатов при взаимодействии с клавиатурой.nextResult: function() { var selected = this.state.selected + 1 var start = this.state.start if(selected >= start + this.props.limit) { ++start } if(selected + start < this.state.results.length) { this.setState({selected: selected, start: start}) } }, prevResult: function() { var selected = this.state.selected - 1 var start = this.state.start if(selected < start) { --start } if(selected + start >= 0) { this.setState({selected: selected, start: start}) } },
Просто пропустив все файлы через фильтр:
updateResults: function() { var results = this.props.files.filter(function(file){ return file.file.indexOf(this.state.query) > -1 }, this) this.setState({ results: results }); },
И нарезка результатов на основе
start
иlimit
вrender
методе:render: function() { var files = this.state.results.slice(this.state.start, this.state.start + this.props.limit) return ( <div> <Search onSearch={this.onSearch} onKeyDown={this.onKeyDown} /> <List files={files} selected={this.state.selected - this.state.start} /> </div> ) }
Fiddle, содержащий полный рабочий пример: https://jsfiddle.net/koenpunt/hm1xnpqk/
источник
Попробуйте выполнить фильтрацию перед загрузкой в компонент React и отобразите только разумное количество элементов в компоненте и загрузите больше по запросу. Никто не может просматривать столько элементов одновременно.
Я так не думаю, но не используйте индексы в качестве ключей .
Чтобы узнать настоящую причину, по которой версии для разработки и производства отличаются, вы можете попробовать
profiling
свой код.Загрузите свою страницу, начните запись, внесите изменения, остановите запись, а затем проверьте время. См. Здесь инструкции по профилированию производительности в Chrome .
источник
Для всех, кто борется с этой проблемой, я написал компонент
react-big-list
который обрабатывает списки до 1 миллиона записей.Вдобавок ко всему он имеет некоторые необычные дополнительные функции, такие как:
Мы используем его в производстве в некоторых приложениях, и он отлично работает.
источник
В React есть рекомендательная
react-window
библиотека: https://www.npmjs.com/package/react-windowЭто лучше, чем
react-vitualized
. Можешь попробоватьисточник