Estou implementando uma lista filtrável com React. A estrutura da lista é mostrada na imagem abaixo.
PREMISSA
Aqui está uma descrição de como ele deve funcionar:
- O estado reside no componente de nível mais alto, o
Search
componente. - O estado é descrito da seguinte forma:
{ visível: booleano, arquivos: array, filtrado: matriz, consulta: string, currentSelectedIndex: integer }
files
é um array potencialmente muito grande contendo caminhos de arquivo (10.000 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 poderia ser feito sobre como armazená-los no estado, mas é necessário paracurrentlySelectedIndex
que é o índice do elemento atualmente selecionado na lista filtrada.O usuário digita mais de 2 letras no
Input
componente, a matriz é filtrada e para cada entrada na matriz filtrada umResult
componente é renderizadoCada
Result
componente está exibindo o caminho completo que correspondeu parcialmente à consulta e a parte da correspondência parcial do caminho é destacada. Por exemplo, o DOM de um componente Result, se o usuário tivesse digitado 'le' seria algo assim:<li>this/is/a/fi<strong>le</strong>/path</li>
- Se o usuário pressionar as teclas para cima ou para baixo enquanto o
Input
componente estiver em foco, ascurrentlySelectedIndex
alterações serão feitas com base nafiltered
matriz. Isso faz com que oResult
componente que corresponde ao índice seja marcado como selecionado, causando uma nova renderização
PROBLEMA
Inicialmente testei isso com um array pequeno o suficiente de files
, usando a versão de desenvolvimento do React, e tudo funcionou bem.
O problema apareceu quando tive que lidar com um files
array tão grande quanto 10.000 entradas. Digitar 2 letras na entrada geraria uma grande lista e quando eu pressionasse as teclas para cima e para baixo para navegar, ficaria muito lento.
No início eu não tinha um componente definido para os Result
elementos e estava apenas fazendo a lista em tempo real, a cada renderização do Search
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 o for currentlySelectedIndex
alterado, isso causará uma nova renderização e a lista será recriada a cada vez. Eu pensei que, como eu havia definido um key
valor para cada li
elemento, o React evitaria a renderização de todos os outros li
elementos que não tivessem sua className
alteração, mas aparentemente não foi assim.
Acabei definindo uma classe para os Result
elementos, onde verifica explicitamente se cada Result
elemento deve ser renderizado novamente com base em se foi selecionado anteriormente e com base na entrada do usuário atual:
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 assim:
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 tornou o desempenho um pouco melhor, mas ainda não é bom o suficiente. A coisa foi quando eu testei na versão de produção do React as coisas funcionaram como manteiga, sem lag.
BOTTOMLINE
Essa discrepância 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
Encontrei esta apresentação de Michael Jackson, onde ele aborda um assunto muito semelhante a este: https://youtu.be/7S8v8jfLb1Q?t=26m2s
A solução é muito semelhante à proposta pela resposta de AskarovBeknar , abaixo
ATUALIZAÇÃO 14-4-2018
Uma vez que esta é aparentemente uma pergunta popular e as coisas progrediram desde que a pergunta original foi feita, embora eu o encoraje a assistir ao vídeo no link acima, a fim de obter uma compreensão de um layout virtual, eu também encorajo você a usar o React Virtualized biblioteca se você não quiser reinventar a roda.