react helmet array javascript list reactjs

javascript - helmet - react meta tags



Gran lista de rendimiento con React (8)

  1. Reaccionar en la versión de desarrollo comprueba los prototipos de cada componente para facilitar el proceso de desarrollo, mientras que en la producción se omite.

  2. Filtrar la lista de cadenas es una operación muy costosa para cada keyup. puede causar problemas de rendimiento debido a la naturaleza de un solo subproceso de JavaScript. La solución podría ser utilizar el método antirrebote para retrasar la ejecución de su función de filtro hasta que expire el retraso.

  3. Otro problema podría ser la enorme lista en sí. Puede crear un diseño virtual y reutilizar los elementos creados simplemente reemplazando los datos. Básicamente, crea un componente contenedor desplazable con altura fija, dentro del cual colocará el contenedor de lista. La altura del contenedor de la lista debe establecerse manualmente (itemHeight * numberOfItems) dependiendo de la longitud de la lista visible, para que funcione una barra de desplazamiento. Luego, cree algunos componentes de elementos para que llenen la altura de los contenedores desplazables y tal vez agreguen uno o dos efectos de lista continua imitados adicionales. hágales la posición absoluta y en el desplazamiento simplemente mueva su posición para que imite la lista continua (creo que descubrirá cómo implementarla :)

  4. Una cosa más es que escribir en DOM también es una operación costosa, especialmente si lo haces mal. Puede usar el lienzo para mostrar listas y crear una experiencia fluida en el desplazamiento. Comprobación de componentes react-canvas. Escuché que ya han trabajado en Listas.

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 nivel más alto, el componente de Search .
  • El estado se describe de la siguiente manera:

{ visible : boolean, files : array, filtered : array, query : string, currentlySelectedIndex : integer }

  • files es una matriz potencialmente muy grande, que contiene rutas de archivos (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 el almacenamiento en el estado, pero es necesario para
  • currentlySelectedIndex que es el índice del elemento actualmente seleccionado de la lista filtrada.

  • El usuario escribe más de 2 letras en el componente Input , la matriz se filtra y para cada entrada en la matriz filtrada se representa un componente Result

  • Cada componente de Result muestra la ruta completa que coincide parcialmente con la consulta y se resalta la parte de coincidencia parcial de la ruta. Por ejemplo, el DOM de un componente Resultado, 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 componente de Input está enfocado, el índice currentlySelectedIndex cambia según la matriz filtered . Esto hace que el componente Result que coincide con el índice se marque como seleccionado, lo que provoca una nueva representación

PROBLEMA

Inicialmente probé esto con una pequeña variedad de files , usando la versión de desarrollo de React, y todo funcionó bien.

El problema apareció cuando tuve que lidiar con una matriz de files tan grande como 10000 entradas. Escribir 2 letras en la entrada generaría una gran lista y cuando presioné las teclas arriba y abajo para navegar, sería muy lento.

Al principio no tenía un componente definido para los elementos de Result y simplemente estaba haciendo la lista sobre la marcha, en cada render del componente de Search , 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 currentlySelectedIndex cambia el índice currentlySelectedIndex , provocaría una nueva representación y la lista se volvería a crear cada vez. Pensé que, dado que había establecido un valor key en cada elemento li , React evitaría volver a representar cualquier otro elemento li que no tuviera su cambio className , pero aparentemente no fue así.

Terminé definiendo una clase para los elementos de Result , donde verifica explícitamente si cada elemento de Result debe volver a representarse 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 cuando probé en la versión de producción de React, las cosas funcionaron sin problemas, sin retraso alguno.

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

Encontré 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 answer de AskarovBeknar, a continuación.

ACTUALIZACIÓN 14-4-2018

Dado que aparentemente esta es una pregunta popular y las cosas han progresado desde que se hizo la pregunta original, si bien lo aliento a que vea el video vinculado anteriormente, para obtener un diseño virtual, también lo aliento a utilizar React Virtualized. biblioteca si no desea reinventar la rueda.


Al igual que con muchas de las otras respuestas a esta pregunta, el problema principal radica en el hecho de que representar tantos elementos en el DOM mientras se filtran y manejan eventos clave será lento.

No está haciendo nada intrínsecamente incorrecto con respecto a React que está causando el problema, pero como muchos de los problemas relacionados con el rendimiento, la interfaz de usuario también puede asumir un gran porcentaje de la culpa.

Si su interfaz de usuario no está diseñada teniendo en cuenta la eficiencia, incluso las herramientas como React que están diseñadas para ser eficaces sufrirán.

Filtrar el conjunto de resultados es un gran comienzo como lo menciona @Koen

Jugué un poco con la idea y creé una aplicación de ejemplo que ilustra cómo podría comenzar a abordar este tipo de problema.

Este no es un código production ready , pero ilustra el concepto de manera adecuada y puede modificarse para que sea más robusto, no dude en echar un vistazo al código; espero que al menos le brinde algunas ideas ...;)

https://github.com/deowk/react-large-list-example


Como mencioné en mi comentario , dudo que los usuarios necesiten todos esos 10000 resultados en el navegador a la vez.

¿Qué sucede si hojea los resultados y siempre muestra una lista de 10 resultados?

He creado un ejemplo usando esta técnica, sin usar ninguna otra biblioteca como Redux. Actualmente solo con navegación por teclado, pero también podría ampliarse fácilmente para trabajar en el desplazamiento.

El ejemplo existe de 3 componentes, la aplicación contenedor, un componente de búsqueda y un componente de lista. Casi toda la lógica se ha movido al componente contenedor.

La esencia radica en hacer un seguimiento del start y el resultado selected , y cambiar aquellos en la interacción del teclado.

nextResult: function() { var selected = this.state.selected + 1 var start = this.state.start if(selected >= start + this.props.limit) { ++start } if(selected + start < this.state.results.length) { this.setState({selected: selected, start: start}) } }, prevResult: function() { var selected = this.state.selected - 1 var start = this.state.start if(selected < start) { --start } if(selected + start >= 0) { this.setState({selected: selected, start: start}) } },

Mientras simplemente pasa todos los archivos a través de un filtro:

updateResults: function() { var results = this.props.files.filter(function(file){ return file.file.indexOf(this.state.query) > -1 }, this) this.setState({ results: results }); },

Y dividiendo los resultados según el start y el limit en el método de render :

render: function() { var files = this.state.results.slice(this.state.start, this.state.start + this.props.limit) return ( <div> <Search onSearch={this.onSearch} onKeyDown={this.onKeyDown} /> <List files={files} selected={this.state.selected - this.state.start} /> </div> ) }

Fiddle que contiene un ejemplo de trabajo completo: https://jsfiddle.net/koenpunt/hm1xnpqk/


Echa un vistazo a React Virtualized Select, está diseñado para abordar este problema y funciona de manera impresionante en mi experiencia. De la descripción:

HOC que utiliza react-virtualized y react-select para mostrar grandes listas de opciones en un menú desplegable

https://github.com/bvaughn/react-virtualized-select


En primer lugar, la diferencia entre la versión de desarrollo y producción de React es enorme porque en la producción hay muchos controles de cordura anulados (como la verificación de tipos de utilería).

Entonces, creo que debería reconsiderar el uso de Redux porque sería extremadamente útil aquí para lo que necesita (o cualquier tipo de implementación de flujo). Definitivamente deberías echar un vistazo a esta presentación: Big List High Performance React & Redux .

Pero antes de sumergirse en redux, debe hacer algunos ajustes en su código React dividiendo sus componentes en componentes más pequeños porque shouldComponentUpdate totalmente la representación de los niños, por lo que es una gran ganancia .

Cuando tiene componentes más granulares, puede manejar el estado con redux y react-redux para organizar mejor el flujo de datos.

Recientemente enfrenté un problema similar cuando necesitaba renderizar mil filas y poder modificar cada fila editando su contenido. Esta mini aplicación muestra una lista de conciertos con posibles conciertos duplicados y necesito elegir cada duplicado potencial si deseo marcar el posible duplicado como un concierto original (no un duplicado) marcando la casilla de verificación y, si es necesario, edite el Nombre del concierto. Si no hago nada por un elemento duplicado potencial particular, se considerará duplicado y se eliminará.

Así es como se ve:

Básicamente hay 4 componentes principales (solo hay una fila aquí, pero es por el bien del ejemplo):

Aquí está el código completo (CodePen de trabajo: Lista enorme con React & Redux ) usando redux , react-redux , immutable , reselect y recompose :

const initialState = Immutable.fromJS({ /* See codepen, this is a HUGE list */ }) const types = { CONCERTS_DEDUP_NAME_CHANGED: ''diggger/concertsDeduplication/CONCERTS_DEDUP_NAME_CHANGED'', CONCERTS_DEDUP_CONCERT_TOGGLED: ''diggger/concertsDeduplication/CONCERTS_DEDUP_CONCERT_TOGGLED'', }; const changeName = (pk, name) => ({ type: types.CONCERTS_DEDUP_NAME_CHANGED, pk, name }); const toggleConcert = (pk, toggled) => ({ type: types.CONCERTS_DEDUP_CONCERT_TOGGLED, pk, toggled }); const reducer = (state = initialState, action = {}) => { switch (action.type) { case types.CONCERTS_DEDUP_NAME_CHANGED: return state .updateIn([''names'', String(action.pk)], () => action.name) .set(''_state'', ''not_saved''); case types.CONCERTS_DEDUP_CONCERT_TOGGLED: return state .updateIn([''concerts'', String(action.pk)], () => action.toggled) .set(''_state'', ''not_saved''); default: return state; } }; /* configureStore */ const store = Redux.createStore( reducer, initialState ); /* SELECTORS */ const getDuplicatesGroups = (state) => state.get(''duplicatesGroups''); const getDuplicateGroup = (state, name) => state.getIn([''duplicatesGroups'', name]); const getConcerts = (state) => state.get(''concerts''); const getNames = (state) => state.get(''names''); const getConcertName = (state, pk) => getNames(state).get(String(pk)); const isConcertOriginal = (state, pk) => getConcerts(state).get(String(pk)); const getGroupNames = reselect.createSelector( getDuplicatesGroups, (duplicates) => duplicates.flip().toList() ); const makeGetConcertName = () => reselect.createSelector( getConcertName, (name) => name ); const makeIsConcertOriginal = () => reselect.createSelector( isConcertOriginal, (original) => original ); const makeGetDuplicateGroup = () => reselect.createSelector( getDuplicateGroup, (duplicates) => duplicates ); /* COMPONENTS */ const DuplicatessTableRow = Recompose.onlyUpdateForKeys([''name''])(({ name }) => { return ( <tr> <td>{name}</td> <DuplicatesRowColumn name={name}/> </tr> ) }); const PureToggle = Recompose.onlyUpdateForKeys([''toggled''])(({ toggled, ...otherProps }) => ( <input type="checkbox" defaultChecked={toggled} {...otherProps}/> )); /* CONTAINERS */ let DuplicatesTable = ({ groups }) => { return ( <div> <table className="pure-table pure-table-bordered"> <thead> <tr> <th>{''Concert''}</th> <th>{''Duplicates''}</th> </tr> </thead> <tbody> {groups.map(name => ( <DuplicatesTableRow key={name} name={name} /> ))} </tbody> </table> </div> ) }; DuplicatesTable.propTypes = { groups: React.PropTypes.instanceOf(Immutable.List), }; DuplicatesTable = ReactRedux.connect( (state) => ({ groups: getGroupNames(state), }) )(DuplicatesTable); let DuplicatesRowColumn = ({ duplicates }) => ( <td> <ul> {duplicates.map(d => ( <DuplicateItem key={d} pk={d}/> ))} </ul> </td> ); DuplicatessRowColumn.propTypes = { duplicates: React.PropTypes.arrayOf( React.PropTypes.string ) }; const makeMapStateToProps1 = (_, { name }) => { const getDuplicateGroup = makeGetDuplicateGroup(); return (state) => ({ duplicates: getDuplicateGroup(state, name) }); }; DuplicatesRowColumn = ReactRedux.connect(makeMapStateToProps1)(DuplicatesRowColumn); let DuplicateItem = ({ pk, name, toggled, onToggle, onNameChange }) => { return ( <li> <table> <tbody> <tr> <td>{ toggled ? <input type="text" value={name} onChange={(e) => onNameChange(pk, e.target.value)}/> : name }</td> <td> <PureToggle toggled={toggled} onChange={(e) => onToggle(pk, e.target.checked)}/> </td> </tr> </tbody> </table> </li> ) } const makeMapStateToProps2 = (_, { pk }) => { const getConcertName = makeGetConcertName(); const isConcertOriginal = makeIsConcertOriginal(); return (state) => ({ name: getConcertName(state, pk), toggled: isConcertOriginal(state, pk) }); }; DuplicateItem = ReactRedux.connect( makeMapStateToProps2, (dispatch) => ({ onNameChange(pk, name) { dispatch(changeName(pk, name)); }, onToggle(pk, toggled) { dispatch(toggleConcert(pk, toggled)); } }) )(DuplicateItem); const App = () => ( <div style={{ maxWidth: ''1200px'', margin: ''auto'' }}> <DuplicatesTable /> </div> ) ReactDOM.render( <ReactRedux.Provider store={store}> <App/> </ReactRedux.Provider>, document.getElementById(''app'') );

Lecciones aprendidas al hacer esta mini aplicación cuando se trabaja con un gran conjunto de datos

  • Los componentes de reacción funcionan mejor cuando se mantienen pequeños
  • Volver a seleccionar se vuelve muy útil para evitar la recalculación y mantener el mismo objeto de referencia (cuando se usa immutable.js) con los mismos argumentos.
  • Cree un componente connect para el componente que sea el más cercano de los datos que necesitan para evitar que el componente solo transmita accesorios que no utilizan
  • El uso de la función de tela para crear mapDispatchToProps cuando solo necesita el accesorio inicial proporcionado en ownProps es necesario para evitar una representación inútil
  • ¡Reacciona y reduce definitivamente rockea juntos!

Intente filtrar antes de cargar en el componente Reaccionar y solo muestre una cantidad razonable de elementos en el componente y cargue más a pedido. Nadie puede ver tantos elementos a la vez.

No creo que lo seas, pero no uses índices como claves .

Para descubrir la verdadera razón por la cual las versiones de desarrollo y producción son diferentes, puede intentar profiling su código.

Cargue su página, comience a grabar, realice un cambio, detenga la grabación y luego revise los tiempos. Consulte aquí las instrucciones para la creación de perfiles de rendimiento en Chrome .


Mi experiencia con un problema muy similar es que la reacción realmente sufre si hay más de 100-200 componentes en el DOM a la vez. Incluso si tiene mucho cuidado (configurando todas sus claves y / o implementando un método shouldComponentUpdate ) para cambiar solo uno o dos componentes en un renderizado, todavía estará en un mundo de dolor.

La parte lenta de reaccionar en este momento es cuando compara la diferencia entre el DOM virtual y el DOM real. Si tiene miles de componentes pero solo actualiza un par, no importa, reaccionar todavía tiene una operación de diferencia masiva que hacer entre los DOM.

Cuando escribo páginas ahora trato de diseñarlas para minimizar el número de componentes, una forma de hacerlo cuando renderizo grandes listas de componentes es ... bueno ... no renderizar grandes listas de componentes.

Lo que quiero decir es: solo renderice los componentes que puede ver actualmente, renderice más a medida que se desplaza hacia abajo, es probable que su usuario no se desplace hacia abajo a través de miles de componentes de ninguna manera ... espero.

Una gran biblioteca para hacer esto es:

https://www.npmjs.com/package/react-infinite-scroll

Con un gran cómo hacerlo aquí:

http://www.reactexamples.com/react-infinite-scroll/

Sin embargo, me temo que no elimina los componentes que están fuera de la parte superior de la página, por lo que si se desplaza el tiempo suficiente, sus problemas de rendimiento comenzarán a resurgir.

Sé que no es una buena práctica proporcionar un enlace como respuesta, pero los ejemplos que proporcionan explicarán cómo usar esta biblioteca mucho mejor de lo que puedo aquí. Espero haber explicado por qué las grandes listas son malas, pero también son una solución.


Para cualquiera que esté luchando con este problema, he escrito un componente react-big-list que maneja listas de hasta 1 millón de registros.

Además de eso, viene con algunas características adicionales como:

  • Clasificación
  • Almacenamiento en caché
  • Filtrado personalizado
  • ...

Lo estamos usando en producción en algunas aplicaciones y funciona muy bien.