javascript reactjs immutable.js

javascript - Rendimiento de reacción: representación de lista grande con PureRenderMixin



reactjs immutable.js (2)

Tomé un ejemplo de TodoList para reflejar mi problema, pero obviamente mi código del mundo real es más complejo.

Tengo un pseudo-código como este.

var Todo = React.createClass({ mixins: [PureRenderMixin], ............ } var TodosContainer = React.createClass({ mixins: [PureRenderMixin], renderTodo: function(todo) { return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>; }, render: function() { var todos = this.props.todos.map(this.renderTodo) return ( <ReactCSSTransitionGroup transitionName="transition-todo"> {todos} </ReactCSSTransitionGroup>, ); } });

Todos mis datos son inmutables, y PureRenderMixin se usa apropiadamente y todo funciona bien. Cuando se modifican los datos de Todo, solo se vuelven a generar los elementos principales y el proceso editado.

El problema es que en algún momento mi lista crece a medida que el usuario se desplaza. Y cuando se actualiza un solo Todo, se tarda más y más tiempo en renderizar el elemento principal, se llama a shouldComponentUpdate en todos los elementos y se procesa el proceso único.

Como puede ver, el componente Todo tiene otro componente que los datos de Todo. Estos son datos que se requieren para el procesamiento por todos los todos y se comparten (por ejemplo, podríamos imaginar que hay un "displayMode" para los todos). Tener muchas propiedades hace que shouldComponentUpdate un poco más lento.

Además, el uso de ReactCSSTransitionGroup parece ralentizarse un poco también, ya que ReactCSSTransitionGroup tiene que renderizarse y ReactCSSTransitionGroupChild incluso antes de que se shouldComponentUpdate la función shouldComponentUpdate of todos. React.addons.Perf muestra que ReactCSSTransitionGroup > ReactCSSTransitionGroupChild representa el tiempo perdido para cada elemento de la lista.

Entonces, hasta donde yo sé, uso PureRenderMixin pero con una lista más grande esto puede no ser suficiente. Aún obtengo actuaciones no tan malas, pero me gustaría saber si hay formas fáciles de optimizar mis representaciones.

¿Alguna idea?

Editar :

Hasta ahora, mi gran lista está paginada, así que en lugar de tener una gran lista de artículos, ahora dividí esta gran lista en una lista de páginas. Esto permite tener mejores resultados ya que cada página ahora puede implementar shouldComponentUpdate . Ahora, cuando un elemento cambia en una página, React solo tiene que llamar a la función de representación principal que itera en la página, y solo llama a la función de renderización desde una sola página, lo que hace que funcione mucho menos iteración.

Sin embargo, mi rendimiento de representación sigue siendo lineal al número de página (O (n)) que tengo. Entonces, si tengo miles de páginas, sigue siendo el mismo problema :) En mi caso de uso, es poco probable que suceda, pero todavía estoy interesado en una solución mejor.

Estoy bastante seguro de que es posible lograr un rendimiento de representación O (log (n)) donde n es el número de elementos (o páginas), dividiendo una lista grande en un árbol (como una estructura de datos persistente) y donde cada nodo tiene el poder de cortocircuitar el cálculo con shouldComponentUpdate

Sí, estoy pensando en algo parecido a estructuras de datos persistentes como Vector in Scala o Clojure:

Sin embargo, me preocupa React porque, hasta donde sé, puede tener que crear nodos dom intermedios al representar los nodos internos del árbol. Esto puede ser un problema de acuerdo con el uso ( y puede ser resuelto en futuras versiones de React )

Además, como usamos Javascript, me pregunto si Immutable-JS lo admite y hace que los "nodos internos" sean accesibles. Ver: https://github.com/facebook/immutable-js/issues/541

Editar : enlace útil con mis experimentos: ¿Puede una aplicación React-Redux realmente escalar tan bien como, por ejemplo, Backbone? Incluso con reselecto En movil


En nuestro producto también tuvimos problemas relacionados con la cantidad de código que se procesaba, y comenzamos a usar observables (ver este blog ). Esto podría resolver parcialmente su problema, ya que cambiar todo ya no requerirá que el componente principal que contiene la lista se vuelva a generar (pero agregar aún lo hace).

También podría ayudarlo a volver a renderizar la lista más rápido, ya que sus componentes todoItem podrían simplemente devolver falsa cuando los accesorios cambien en shouldComponentUpdate.

Para obtener más mejoras de rendimiento al presentar la vista general, creo que su idea de árbol / paginación es muy buena. Con las matrices observables, cada página podría comenzar a escuchar empalmes de matriz (utilizando un polilíter ES7 o un observable) en un cierto rango. Eso introduciría algo de administración, ya que estos rangos podrían cambiar con el tiempo, pero debería llevarlo a O (log (n))

Entonces obtienes algo como:

var TodosContainer = React.createClass({ componentDidMount() { this.props.todos.observe(function(change) { if (change.type === ''splice'' && change.index >= this.props.startRange && change.index < this.props.endRange) this.forceUpdate(); }); }, renderTodo: function(todo) { return <Todo key={todo.id} todoData={todo} x={this.props.x} y={this.props.y} .../>; }, render: function() { var todos = this.props.todos.slice(this.props.startRange, this.props.endRange).map(this.renderTodo) return ( <ReactCSSTransitionGroup transitionName="transition-todo"> {todos} </ReactCSSTransitionGroup>, ); } });

El problema central con listas grandes y reacción parece que no se puede simplemente cambiar nuevos nodos DOM en el dom. De lo contrario, no necesitará las ''páginas'' para dividir los datos en fragmentos más pequeños y podría simplemente empalmar un nuevo elemento de Todo en el dom, como se hace con JQuery en este jsFiddle . Todavía podrías hacer eso con reaccionar si usas un ref para cada ítem todo, pero eso estaría funcionando alrededor del sistema, creo que podría romper el sistema de reconciliación.


Aquí hay una implementación POC que hice con la estructura interna de ImmutableJS. Esta no es una API pública, por lo que no está lista para producción y actualmente no maneja casos de esquina, pero funciona.

var ImmutableListRenderer = React.createClass({ render: function() { // Should not require to use wrapper <span> here but impossible for now return (<span> {this.props.list._root ? <GnRenderer gn={this.props.list._root}/> : undefined} {this.props.list._tail ? <GnRenderer gn={this.props.list._tail}/> : undefined} </span>); } }) // "Gn" is the equivalent of the "internal node" of the persistent data structure schema of the question var GnRenderer = React.createClass({ shouldComponentUpdate: function(nextProps) { console.debug("should update?",(nextProps.gn !== this.props.gn)); return (nextProps.gn !== this.props.gn); }, propTypes: { gn: React.PropTypes.object.isRequired, }, render: function() { // Should not require to use wrapper <span> here but impossible for now return ( <span> {this.props.gn.array.map(function(gnItem,index) { // TODO should check for Gn instead, because list items can be objects too... var isGn = typeof gnItem === "object" if ( isGn ) { return <GnRenderer gn={gnItem}/> } else { // TODO should be able to customize the item rendering from outside return <span>{" -> " + gnItem}</span> } }.bind(this))} </span> ); } })

El código del cliente se ve como

React.render( <ImmutableListRenderer list={ImmutableList}/>, document.getElementById(''container'') );

Aquí hay un JsFiddle que registra el número de llamadas a shouldComponentUpdate después de shouldComponentUpdate un único elemento de la lista (tamaño N): esto no requiere llamar N veces shouldComponentUpdate

Se comparten más detalles de implementación en este problema de github ImmutableJs