Estoy en el proceso de implementar una lista filtrable con React. La estructura de la lista es como se muestra en la imagen a continuación.
PREMISA
Aquí hay una descripción de cómo se supone que funciona:
- El estado reside en el componente de más alto nivel, el
Search
componente. - El estado se describe de la siguiente manera:
{ visible: booleano, archivos: matriz, filtrado: matriz, consulta: cadena, currentSelectedIndex: integer }
files
es una matriz potencialmente muy grande que contiene rutas de archivo (10000 entradas es un número plausible).filtered
es la matriz filtrada después de que el usuario escribe al menos 2 caracteres. Sé que son datos derivados y, como tal, se podría argumentar sobre almacenarlos en el estado, pero es necesario paracurrentlySelectedIndex
que es el índice del elemento seleccionado actualmente de la lista filtrada.El usuario escribe más de 2 letras en el
Input
componente, la matriz se filtra y por cada entrada en la matriz filtradaResult
se representa un componenteCada
Result
componente muestra la ruta completa que coincidió parcialmente con la consulta, y la parte de coincidencia parcial de la ruta está resaltada. Por ejemplo, el DOM de un componente Result, si el usuario hubiera escrito 'le' sería algo como esto:<li>this/is/a/fi<strong>le</strong>/path</li>
- Si el usuario presiona las teclas arriba o abajo mientras el
Input
componente está enfocado, loscurrentlySelectedIndex
cambios se basan en lafiltered
matriz. Esto hace que elResult
componente que coincide con el índice se marque como seleccionado provocando una re-renderización
PROBLEMA
Inicialmente probé esto con una matriz lo suficientemente pequeña files
, usando la versión de desarrollo de React, y todo funcionó bien.
El problema apareció cuando tuve que lidiar con una files
matriz de hasta 10000 entradas. Escribir 2 letras en la entrada generaría una gran lista y cuando presioné las teclas de arriba y abajo para navegar sería muy lento.
Al principio, no tenía un componente definido para los Result
elementos y simplemente estaba haciendo la lista sobre la marcha, en cada renderizado del 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 puede ver, cada vez que se currentlySelectedIndex
cambia, se vuelve a generar y la lista se vuelve a crear cada vez. Pensé que dado que había establecido un key
valor en cada li
elemento, React evitaría volver a renderizar todos los demás li
elementos que no tuvieran su className
cambio, pero aparentemente no fue así.
Terminé definiendo una clase para los Result
elementos, donde verifica explícitamente si cada Result
elemento debe volver a renderizarse en función de si se seleccionó previamente y en función de la entrada actual del usuario:
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>
);
}
});
Y la lista ahora se crea 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));
}
Esto hizo que el rendimiento fuera un poco mejor, pero aún no es lo suficientemente bueno. La cosa es que cuando probé en la versión de producción de React, las cosas funcionaron perfectamente, sin ningún retraso.
LÍNEA DE FONDO
¿Es normal una discrepancia tan notable entre las versiones de desarrollo y producción de React?
¿Estoy entendiendo / haciendo algo mal cuando pienso en cómo React gestiona la lista?
ACTUALIZACIÓN 14-11-2016
He encontrado esta presentación de Michael Jackson, donde aborda un tema muy similar a este: https://youtu.be/7S8v8jfLb1Q?t=26m2s
La solución es muy similar a la propuesta por la respuesta de AskarovBeknar , a continuación
ACTUALIZACIÓN 14-4-2018
Dado que esta es aparentemente una pregunta popular y las cosas han progresado desde que se hizo la pregunta original, aunque te animo a que veas el video vinculado anteriormente, para que comprendas un diseño virtual, también te animo a usar React Virtualized biblioteca si no quieres reinventar la rueda.