Desempenho de grande lista com o React
Estou no processo de implementar uma lista filtrável com o React. A estrutura da lista é como mostrado na imagem abaixo.
PREMISSA
Aqui está uma descrição de como deve funcionar:
O estado reside no componente de nível mais alto, oSearch
componente.O estado é descrito da seguinte maneira:{ visible : boolean, files : array, filtered : array, query : string, currentlySelectedIndex : integer }
files
é uma matriz potencialmente muito grande que contém caminhos de arquivo (10000 entradas é um número plausível).filtered
é a matriz filtrada após o usuário digitar pelo menos 2 caracteres. Eu sei que são dados derivados e, como tal, um argumento pode ser feito sobre o armazenamento no estado, mas é necessário paracurrentlySelectedIndex
que é o índice do elemento atualmente selecionado da lista filtrada.
O usuário digita mais de 2 letras noInput
componente, a matriz é filtrada e, para cada entrada na matriz filtrada, umResult
componente é renderizado
CadaResult
O componente está exibindo o caminho completo que corresponde parcialmente à consulta e a parte de correspondência parcial do caminho é destacada. Por exemplo, o DOM de um componente Result, se o usuário digitar 'le' seria algo como isto:
<li>this/is/a/fi<strong>le</strong>/path</li>
Input
componente está focado ocurrentlySelectedIndex
mudanças com base nofiltered
array. Isso faz com que oResult
componente que corresponde ao índice a ser marcado como selecionado, causando uma nova renderizaçãoPROBLEMA
Inicialmente, testei isso com uma matriz pequena o suficientefiles
, usando a versão de desenvolvimento do React, e tudo funcionou bem.
O problema apareceu quando tive que lidar com umafiles
matriz tão grande quanto 10000 entradas. Digitar duas letras na entrada geraria uma grande lista e, quando eu pressionasse as teclas para cima e para baixo para navegar, seria muito lento.
No começo, eu não tinha um componente definido para oResult
elementos e eu estava apenas fazendo a lista em tempo real, em cada renderização doSearch
componente, como tal:
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));
Como você pode ver, toda vez que ocurrentlySelectedIndex
alterado, causaria uma nova renderização e a lista seria recriada a cada vez. Eu pensei que desde que eu tinha definido umkey
valor em cadali
O elemento React evitaria renderizar novamente todos os outrosli
elemento que não teve suaclassName
mudar, mas aparentemente não era assim.
Acabei definindo uma aula para oResult
elementos, onde verifica explicitamente se cadaResult
O elemento deve ser renderizado novamente com base em se ele foi selecionado anteriormente e com base na entrada atual do usuário:
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 a lista agora é criada como tal:
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));
}
Isso fez o desempenholevemente melhor, mas ainda não é bom o suficiente. A coisa é quando eu testei na versão de produção do React, as coisas funcionaram bem amanteigadas, sem atrasos.
LINHA INFERIOR
Uma discrepância tão perceptível entre as versões de desenvolvimento e produção do React é normal?
Estou entendendo / fazendo algo errado quando penso em como o React gerencia a lista?
ATUALIZAÇÃO 14-11-2016
Eu encontrei esta apresentação de Michael Jackson, onde ele aborda um problema muito semelhante a este:https://youtu.be/7S8v8jfLb1Q?t=26m2s
A solução é muito semelhante à proposta por AskarovBeknarresponda, abaixo
ATUALIZAÇÃO 14-4-2018
Como essa é aparentemente uma pergunta popular e as coisas progrediram desde que a pergunta original foi feita, embora eu o incentive a assistir ao vídeo link acima, a fim de obter uma idéia de um layout virtual, também o incentivo a usar oReact Virtualized biblioteca se você não deseja reinventar a roda.