Große Listenleistung mit React
Ich bin dabei, mit React eine filterbare Liste zu implementieren. Die Struktur der Liste ist in der folgenden Abbildung dargestellt.
PRÄMISS
Hier ist eine Beschreibung, wie es funktionieren soll:
Der Status befindet sich in der Komponente der höchsten Ebene, demSearch
component. Der Zustand wird wie folgt beschrieben:{ visible : boolean, files : array, filtered : array, query : string, currentlySelectedIndex : integer }
files
ist ein möglicherweise sehr großes Array mit Dateipfaden (10000 Einträge sind eine plausible Zahl).filtered
ist das gefilterte Array, nachdem der Benutzer mindestens 2 Zeichen eingegeben hat. Ich weiß, dass es sich um abgeleitete Daten handelt, und als solches könnte man argumentieren, sie in dem Zustand zu speichern, aber sie werden für @ benötigcurrentlySelectedIndex
Dies ist der Index des aktuell ausgewählten Elements aus der gefilterten Liste.
Benutzer tippt mehr als 2 Buchstaben in dasInput
-Komponente wird das Array gefiltert und für jeden Eintrag im gefilterten Array einResult
Komponente wird gerendert
JederResult
ie @ -Komponente zeigt den vollständigen Pfad an, der teilweise mit der Abfrage übereinstimmt, und der teilweise übereinstimmende Teil des Pfads wird hervorgehoben. Zum Beispiel wäre das DOM einer Result-Komponente, wenn der Benutzer 'le' eingegeben hätte, ungefähr so:
<li>this/is/a/fi<strong>le</strong>/path</li>
Input
-Komponente ist fokussiert diecurrentlySelectedIndex
Änderungen basierend auf demfiltered
array. Dies bewirkt, dass dasResult
-Komponente, die mit dem Index übereinstimmt, der als ausgewählt markiert werden soll, wodurch ein erneutes Rendern ausgelöst wirdPROBLE
Initial habe ich dies mit einem ausreichend kleinen Array von @ getestfiles
, mit der Entwicklungsversion von React, und alles hat gut funktioniert.
Das Problem trat auf, als ich mich mit einem @ auseinandersetzen mussfiles
Array so groß wie 10000 Einträge. Wenn Sie 2 Buchstaben in die Eingabe eingeben, wird eine große Liste erstellt, und wenn ich zum Navigieren die Auf- und Ab-Tasten drücke, wird dies sehr verzögert.
Zunächst hatte ich keine definierte Komponente für dasResult
elements und ich haben die Liste nur im Handumdrehen erstellt, bei jedem Rendern desSearch
-Komponente als solche:
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));
Wie Sie sehen können, jedes Mal, wenn dascurrentlySelectedIndex
geändert, würde es ein erneutes Rendern verursachen und die Liste würde jedes Mal neu erstellt. Ich dachte, da ich ein @ gesetzt hatkey
Wert auf jedemli
element React würde es vermeiden, jedes andere @ erneut zu renderli
Element, das sein @ nicht hatclassName
ändern, aber anscheinend war es nicht so.
m Ende definierte ich eine Klasse für dasResult
-Elemente, bei denen explizit geprüft wird, ob jedesResult
-Element sollte basierend darauf, ob es zuvor ausgewählt wurde und basierend auf der aktuellen Benutzereingabe neu gerendert werden:
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>
);
}
});
Und die Liste wird nun als solche erstellt:
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));
}
Diese Leistung gemachtleich besser, aber es ist immer noch nicht gut genug. Als ich die Serienversion von React ausprobierte, lief alles reibungslos und ohne Verzögerung.
ENDEFFEK
Ist eine solche auffällige Diskrepanz zwischen Entwicklungs- und Produktionsversion von React normal?
Verstehe / tue ich etwas falsch, wenn ich darüber nachdenke, wie React die Liste verwaltet?
UPDATE 14-11-2016
Ich habe diese Präsentation von Michael Jackson gefunden, in der er sich mit einem ähnlichen Thema befasst:https: //youtu.be/7S8v8jfLb1Q? t = 26m2s
Die Lösung ist der von AskarovBeknar vorgeschlagenen sehr ähnlichAntworte, unter
UPDATE 14-4-2018
Da dies anscheinend eine beliebte Frage ist und sich die Dinge seit der ursprünglichen Frage weiterentwickelt haben, empfehle ich Ihnen, das oben verlinkte Video anzuschauen, um ein virtuelles Layout zu erhalten. Ich empfehle Ihnen auch, das @ zu verwendeReact Virtualized Bibliothek, wenn Sie das Rad nicht neu erfinden möchten.