it-swarm.com.de

Reagieren Sie auf Leistung: Rendern einer großen Liste mit PureRenderMixin

Ich habe ein TodoList-Beispiel genommen, um mein Problem zu reflektieren, aber mein realer Code ist offensichtlich komplexer.

Ich habe einen Pseudo-Code wie diesen.

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>,
     );
  }

});

Alle meine Daten sind unveränderlich und PureRenderMixin wird ordnungsgemäß verwendet und alles funktioniert einwandfrei. Wenn Todo-Daten geändert werden, werden nur der übergeordnete und der bearbeitete ToDo-Vorgang erneut gerendert.

Das Problem ist, dass meine Liste irgendwann mit dem Scrollen des Benutzers immer größer wird. Und wenn ein einzelner Todo aktualisiert wird, dauert es immer mehr, das übergeordnete Element zu rendern, shouldComponentUpdate für alle ToDos aufzurufen und dann das einzelne ToDo zu rendern.

Wie Sie sehen, hat die Todo-Komponente eine andere Komponente als die Todo-Daten. Dies sind Daten, die für das Rendern von allen ToDos erforderlich sind und gemeinsam genutzt werden (beispielsweise könnten wir uns vorstellen, dass es einen "DisplayMode" für die ToDos gibt). Aufgrund vieler Eigenschaften ist shouldComponentUpdate etwas langsamer. 

Die Verwendung von ReactCSSTransitionGroup scheint sich ebenfalls etwas zu verlangsamen, da ReactCSSTransitionGroup selbst und ReactCSSTransitionGroupChild gerendert werden muss, noch bevor die shouldComponentUpdate von todos aufgerufen wird. React.addons.Perf zeigt, dass ReactCSSTransitionGroup > ReactCSSTransitionGroupChild-Rendering Zeit für jedes Element der Liste ist.

Soweit ich weiß, verwende ich PureRenderMixin, aber bei einer größeren Liste reicht dies möglicherweise nicht aus. Ich bekomme immer noch nicht so schlechte Leistungen, würde aber gerne wissen, ob es einfache Möglichkeiten gibt, meine Renderings zu optimieren.

Irgendeine Idee?


Bearbeiten:

Bisher ist meine große Liste paginiert, und statt einer großen Liste von Elementen habe ich diese große Liste nun in eine Liste von Seiten aufgeteilt. Dies ermöglicht bessere Leistungen, da nun auf jeder Seite shouldComponentUpdate implementiert werden kann. Wenn nun ein Element auf einer Seite geändert wird, muss React nur die auf der Seite durchlaufende Render-Hauptfunktion und nur die Render-Funktion von einer einzigen Seite aus aufrufen, wodurch die Iterationsarbeit erheblich reduziert wird.

Meine Renderleistung ist jedoch immer noch linear zur Seitennummer (O (n)), die ich habe. Wenn ich also Tausende von Seiten habe, ist es immer noch das gleiche Problem :) In meiner Anwendung wird dies unwahrscheinlich passieren, aber ich bin immer noch an einer besseren Lösung interessiert.

Ich bin mir ziemlich sicher, dass es möglich ist, O(log(n)) Rendering-Leistung zu erreichen, wobei n die Anzahl der Elemente (oder Seiten) ist, indem eine große Liste in einen Baum (wie eine persistente Datenstruktur) aufgeteilt wird. und wo jeder Knoten die Berechnung mit shouldComponentUpdate kurzschließen kann 

Ja, ich denke an etwas, das an persistente Datenstrukturen wie Vector in Scala oder Clojure erinnert:

http://hypirion.com/imgs/pvec/9-annotated.png

Ich mache mir jedoch Sorgen um React, da meines Wissens nach beim Rendern der internen Knoten des Baums zwischengeordnete Domknoten erstellt werden müssen. Dies kann gemäß der Usecase ein Problem sein ( und in zukünftigen Versionen von React gelöst werden)

Da wir Javascript verwenden, frage ich mich, ob Immutable-JS dies unterstützt und die "internen Knoten" zugänglich macht. Siehe: https://github.com/facebook/immutable-js/issues/541

Edit: nützlicher Link zu meinen Experimenten: Kann eine React-Redux-App wirklich skaliert werden und etwa Backbone sagen? Auch bei Neuwahl. Auf dem Handy

24

Hier ist eine POC-Implementierung, die ich mit der internen Struktur von ImmutableJS durchgeführt habe. Dies ist keine öffentliche API, daher ist sie nicht produktionsbereit und behandelt derzeit keine Eckfälle, aber sie funktioniert.

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>
        );
    }
})

Der Client-Code sieht aus wie:

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

Hier ist ein JsFiddle , das die Anzahl der shouldComponentUpdate-Aufrufe protokolliert, nachdem ein einzelnes Element der Liste (Größe N) aktualisiert wurde: Es ist nicht erforderlich, N-mal aufzurufen shouldComponentUpdate 

Weitere Details zur Implementierung sind in dieser Ausgabe enthalten. ImmutableJs github issue

5

In unserem Produkt hatten wir auch Probleme mit der Menge des gerenderten Codes und wir begannen mit der Verwendung von Observables (siehe dieses blog ). Dies kann Ihr Problem teilweise lösen, da für das Ändern von Aufgaben nicht mehr erforderlich ist, dass die übergeordnete Komponente, die die Liste enthält, erneut gerendert wird (das Hinzufügen jedoch immer noch erfolgt).

Es kann auch helfen, die Liste schneller wiederzugeben, da Ihre todoItem-Komponenten nur dann false zurückgeben können, wenn sich die Requisiten auf shouldComponentUpdate ändern.

Für weitere Leistungsverbesserungen beim Rendern der Übersicht halte ich Ihre Baum-/Paging-Idee für Nizza. Bei beobachtbaren Arrays könnte jede Seite beginnen, Arrayspleiße (mit einer ES7-Polyfill oder -Mobservable) in einem bestimmten Bereich zu hören. Das würde etwas Administration einführen, da sich diese Bereiche im Laufe der Zeit ändern können, aber Sie sollten zu O (log (n)) gelangen.

Also bekommst du so etwas wie:

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>,
     );
  }

});

Das zentrale Problem bei großen Listen und Reaktionen scheint, dass Sie neue DOM-Knoten nicht einfach in den Dom verschieben können. Andernfalls brauchen Sie die "Seiten" überhaupt nicht, um die Daten in kleinere Abschnitte aufzuteilen, und Sie können einfach ein neues Todo-Element in den Dom einspalten, wie dies bei JQuery in diesem jsFiddle der Fall ist. Sie könnten das immer noch mit React tun, wenn Sie eine ref für jedes ToDo-Element verwenden, aber das würde das System umgehen, denke ich, da es das Abstimmungssystem zerstören könnte.

9
mweststrate

Vor kurzem hatte ich einen Leistungsengpass, als ich versuchte, eine Tabelle mit mehr als 500 Datensätzen zu rendern. Die Reduzierer waren unveränderlich, und ich verwendete Reselect, um die komplexen Selektoren zu speichern.

0
Kornelius