Sto implementando un elenco filtrabile con React. La struttura dell'elenco è come mostrato nell'immagine sottostante.
PREMESSA
Ecco una descrizione di come dovrebbe funzionare:
- Lo stato risiede nella componente di livello più alto, la
Searchcomponente. - Lo stato è descritto come segue:
{
visibile: booleano,
file: array,
filtrato: array,
stringa della domanda,
currentlySelectedIndex: intero
}
filesè un array potenzialmente molto grande, contenente percorsi di file (10000 voci è un numero plausibile).filteredè l'array filtrato dopo che l'utente ha digitato almeno 2 caratteri. So che sono dati derivati e come tale si potrebbe argomentare sulla memorizzazione nello stato, ma è necessario percurrentlySelectedIndexche è l'indice dell'elemento attualmente selezionato dall'elenco filtrato.L'utente digita più di 2 lettere nel
Inputcomponente, l'array viene filtrato e per ogni voce nell'array filtratoResultviene visualizzato un componenteOgni
Resultcomponente visualizza il percorso completo che corrisponde parzialmente alla query e la parte di corrispondenza parziale del percorso viene evidenziata. Ad esempio, il DOM di un componente Risultato, se l'utente avesse digitato "le" sarebbe qualcosa del genere:<li>this/is/a/fi<strong>le</strong>/path</li>- Se l'utente preme i tasti su o giù mentre il
Inputcomponente è focalizzato, lecurrentlySelectedIndexmodifiche si basanofilteredsull'array. Ciò fa sì che ilResultcomponente che corrisponde all'indice venga contrassegnato come selezionato provocando un nuovo rendering
PROBLEMA
Inizialmente l'ho testato con un array abbastanza piccolo di files, utilizzando la versione di sviluppo di React, e tutto ha funzionato bene.
Il problema è apparso quando ho dovuto gestire un filesarray grande quanto 10000 voci. Digitare 2 lettere nell'Input genererebbe un grande elenco e quando premevo i tasti su e giù per spostarlo sarebbe stato molto lento.
All'inizio non avevo un componente definito per gli Resultelementi e stavo semplicemente facendo l'elenco al volo, su ogni render del Searchcomponente, in quanto tale:
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));
Come puoi vedere, ogni volta che la currentlySelectedIndexmodifica viene eseguita, viene eseguito un nuovo rendering e l'elenco viene ricreato ogni volta. Pensavo che poiché avevo impostato un keyvalore su ogni lielemento, React avrebbe evitato di ri-renderizzare ogni altro lielemento che non avesse la sua classNamemodifica, ma a quanto pare non è stato così.
Ho finito per definire una classe per gli Resultelementi, dove controlla esplicitamente se ogni Resultelemento deve essere rieseguito in base al fatto che sia stato precedentemente selezionato e in base all'input dell'utente corrente:
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>
);
}
});
E l'elenco è ora creato come tale:
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));
}
Ciò ha migliorato leggermente le prestazioni , ma non è ancora abbastanza buono. Il fatto è che quando ho provato sulla versione di produzione di React le cose hanno funzionato senza intoppi, senza alcun ritardo.
LINEA DI FONDO
È normale una discrepanza così evidente tra le versioni di sviluppo e di produzione di React?
Sto capendo / facendo qualcosa di sbagliato quando penso a come React gestisce l'elenco?
AGGIORNAMENTO 14-11-2016
Ho trovato questa presentazione di Michael Jackson, dove affronta un problema molto simile a questo: https://youtu.be/7S8v8jfLb1Q?t=26m2s
La soluzione è molto simile a quella proposta dalla risposta di AskarovBeknar , di seguito
AGGIORNAMENTO 14-4-2018
Poiché questa è apparentemente una domanda popolare e le cose sono progredite da quando è stata posta la domanda originale, mentre ti incoraggio a guardare il video collegato sopra, al fine di avere un'idea di un layout virtuale, ti incoraggio anche a usare React Virtualized biblioteca se non vuoi reinventare la ruota.



