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
Search
Komponente. - Der Zustand wird wie folgt beschrieben:
{ sichtbar: boolean, Dateien: Array, gefiltert: Array, Abfrage: Zeichenfolge, currentSelectedIndex: Ganzzahl }}
files
ist ein potenziell 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 im Staat zu speichern, aber es wird für benötigtcurrentlySelectedIndex
Dies ist der Index des aktuell ausgewählten Elements aus der gefilterten Liste.Der Benutzer gibt mehr als 2 Buchstaben in die
Input
Komponente ein, das Array wird gefiltert und für jeden Eintrag im gefilterten Array wird eineResult
Komponente gerendertJede
Result
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 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
Input
Komponente fokussiert ist, werden diecurrentlySelectedIndex
Änderungen basierend auf demfiltered
Array vorgenommen. Dadurch wird dieResult
Komponente, 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 files
unter Verwendung der Entwicklungsversion von React getestet , und alles hat gut funktioniert.
Das Problem trat auf, als ich mich mit einem files
Array 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 Result
Elemente und ich erstellte lediglich die Liste im laufenden Betrieb bei jedem Rendering der Search
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, 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 key
für jedes li
Element einen Wert festgelegt hatte, würde React vermeiden, jedes andere li
Element neu zu rendern , das seine className
Änderung nicht hatte, aber anscheinend war es nicht so.
Am Ende habe ich eine Klasse für die Result
Elemente definiert, in der explizit geprüft wird, ob jedes Result
Element 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.