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
Search
componente. - 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 percurrentlySelectedIndex
che è l'indice dell'elemento attualmente selezionato dall'elenco filtrato.L'utente digita più di 2 lettere nel
Input
componente, l'array viene filtrato e per ogni voce nell'array filtratoResult
viene visualizzato un componenteOgni
Result
componente 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
Input
componente è focalizzato, lecurrentlySelectedIndex
modifiche si basanofiltered
sull'array. Ciò fa sì che ilResult
componente 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 files
array 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 Result
elementi e stavo semplicemente facendo l'elenco al volo, su ogni render del Search
componente, 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 currentlySelectedIndex
modifica viene eseguita, viene eseguito un nuovo rendering e l'elenco viene ricreato ogni volta. Pensavo che poiché avevo impostato un key
valore su ogni li
elemento, React avrebbe evitato di ri-renderizzare ogni altro li
elemento che non avesse la sua className
modifica, ma a quanto pare non è stato così.
Ho finito per definire una classe per gli Result
elementi, dove controlla esplicitamente se ogni Result
elemento 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.