Ich bin dabei, eine filterbare Liste mit React zu implementieren. Die Struktur der Liste ist wie in der Abbildung unten dargestellt.
PRÄMISSE
Hier ist eine Beschreibung, wie es funktionieren soll:
- Der Status befindet sich in der Komponente der höchsten Ebene, der
SearchKomponente. - Der Zustand wird wie folgt beschrieben:
{
sichtbar: boolean,
Dateien: Array,
gefiltert: Array,
Abfrage: Zeichenfolge,
currentSelectedIndex: Ganzzahl
}}
filesist ein potenziell sehr großes Array mit Dateipfaden (10000 Einträge sind eine plausible Zahl).filteredist 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 im Staat zu speichern, aber es wird für benötigtcurrentlySelectedIndexDies ist der Index des aktuell ausgewählten Elements aus der gefilterten Liste.Der Benutzer gibt mehr als 2 Buchstaben in die
InputKomponente ein, das Array wird gefiltert und für jeden Eintrag im gefilterten Array wird eineResultKomponente gerendertJede
ResultKomponente 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 Ergebniskomponente, wenn der Benutzer 'le' eingegeben hätte, ungefähr so:<li>this/is/a/fi<strong>le</strong>/path</li>- Wenn der Benutzer die Aufwärts- oder Abwärtstaste drückt, während die
InputKomponente fokussiert ist, werden diecurrentlySelectedIndexÄnderungen basierend auf demfilteredArray vorgenommen. Dadurch wird dieResultKomponente, die dem Index entspricht, als ausgewählt markiert, was zu einem erneuten Rendern führt
PROBLEM
Anfangs habe ich dies mit einem ausreichend kleinen Array filesunter Verwendung der Entwicklungsversion von React getestet , und alles hat gut funktioniert.
Das Problem trat auf, als ich mich mit einem filesArray mit bis zu 10000 Einträgen befassen musste . Wenn Sie 2 Buchstaben in die Eingabe eingeben, wird eine große Liste erstellt, und wenn ich die Auf- und Ab-Tasten zum Navigieren drücke, ist dies sehr verzögert.
Zuerst hatte ich keine definierte Komponente für die ResultElemente und ich erstellte lediglich die Liste im laufenden Betrieb bei jedem Rendering der SearchKomponente 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, würde jedes Mal, wenn die currentlySelectedIndexÄnderung vorgenommen wird, ein erneutes Rendern verursacht und die Liste jedes Mal neu erstellt. Ich dachte, da ich keyfür jedes liElement einen Wert festgelegt hatte, würde React vermeiden, jedes andere liElement neu zu rendern , das seine classNameÄnderung nicht hatte, aber anscheinend war es nicht so.
Am Ende habe ich eine Klasse für die ResultElemente definiert, in der explizit geprüft wird, ob jedes ResultElement neu gerendert werden soll, basierend darauf, ob es zuvor ausgewählt wurde und basierend auf der aktuellen Benutzereingabe:
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 jetzt 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));
}
Dies hat die Leistung etwas verbessert , ist aber immer noch nicht gut genug. Als ich die Produktionsversion von React testete, funktionierten die Dinge butterweich und ohne Verzögerung.
ENDEFFEKT
Ist eine solche merkliche Diskrepanz zwischen Entwicklungs- und Produktionsversionen von React normal?
Verstehe / mache 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 ein Problem behandelt, das diesem sehr ähnlich ist: https://youtu.be/7S8v8jfLb1Q?t=26m2s
Die Lösung ist der in der Antwort von AskarovBeknar unten vorgeschlagenen sehr ähnlich
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 anzusehen, um sich ein Bild von einem virtuellen Layout zu machen. Ich empfehle Ihnen jedoch auch, React Virtualized zu verwenden Bibliothek, wenn Sie das Rad nicht neu erfinden möchten.



