React JS-Rendering verstehen | Komentor

Es gibt also eine neue Architektur in der Stadt, FLUX, und sie beginnt, festen Boden unter den Füßen zu bekommen. Darunter gibt es mehrere Implementierungen davon Facebook-Fluss, Yahoo Dispatchri, Fluxor, McFly und jFlux, an der ich persönlich gearbeitet habe. Eines der Kernkonzepte von FLUX ist Shops. Ein Geschäft gibt immer dann ein Änderungsereignis aus, wenn sich irgendein Zustand innerhalb des Geschäfts ändert. Alle Komponenten, die auf Änderungen an Stores hören, holen sich den Status aus dem jeweiligen Store und rendern. In diesem Artikel werfen wir einen Blick auf das Rendering von React JS, wenn es in einer traditionellen FLUX-Umgebung arbeitet.

Das Change-Event

Beim Lesen von Dokumentationen zur FLUX-Architektur werden Sie sicherlich auf das „Change Event“ stoßen. Grundsätzlich wird jedes Mal, wenn ein Geschäft eine Änderung an einem Zustand vorgenommen hat, diese eine Art von Ereignis ausgegeben. Rückgeld. Wenn Sie nur einen Ereignistyp haben, ist Ihre Anwendung natürlich besser zu verwalten, da alle Ihre Komponenten mit den Geschäften synchron gehalten werden, aber es hat auch seinen Preis. Schauen wir uns ein Beispiel mit a an Flussmittel-Reaktion Laden:

var store = flux.createStore({
  todos: [],
  addTodo: function (todo) {
    this.todos.push(todo);
    this.emit('change');
  },
  removeTodo: function (index) {
    this.todos.splice(index, 1);
    this.emit('change');
  },
  exports: {
    getTodos: function () {
      return this.todos;
    }
  }
});

Alle Komponenten, die auf ein Änderungsereignis in diesem Geschäft lauschen, würden die verwenden getTodos -Methode, wenn Todos hinzugefügt und entfernt werden, um den aktualisierten Status abzurufen. Das macht Sinn und daran ist absolut nichts auszusetzen, da es sehr wahrscheinlich ist, dass jede Komponente, die daran interessiert ist, Todos hinzuzufügen, wahrscheinlich auch an deren Entfernung interessiert wäre. Aber lassen Sie uns einen weiteren Zustand hinzufügen:

var store = flux.createStore({
  todos: [],
  isSaving: false,
  addTodo: function (todo) {
    this.todos.push(todo);
    this.isSaving = true;
    this.emit('change');
    doSomethingAsync().then(function () {
      this.isSaving = false;
      this.emit('change');
    }.bind(this));
  },
  removeTodo: function (index) {
    this.todos.splice(index, 1);
    this.isSaving = true;
    this.emit('change');
    doSomethingAsync().then(function () {
      this.isSaving = false;
      this.emit('change');
    }.bind(this));
  },
  exports: {
    getTodos: function () {
      return this.todos;
    },
    isSaving: function () {
      return this.isSaving;
    }
  }
});

Jetzt lösen wir zuerst eine Änderung aus, um unseren Komponenten mitzuteilen, dass der Laden in ist istSparen Staat und dass wir eine neue Aufgabe haben. Später benachrichtigen wir unsere Komponenten erneut, dass der Speicher nicht mehr speichert. Anhand dieses einfachen Beispiels beginnen wir zu sehen, wo die Kosten steigen. Sie könnten argumentieren, dass es keine asynchronen Vorgänge in Geschäften geben sollte, aber halten Sie mich daran. In diesem Fall konzentrieren wir uns auf die Verwendung eines allgemeinen „Änderungs“-Ereignisses sowohl für die Benachrichtigung über unsere istSparen Zustand und das Update zu unserem alle Zustand. Lassen Sie uns visualisieren, was ich hier meine. Stellen Sie sich vor, dieser HTML-Code wäre Komponenten:

<div>
  <AddTodoComponent/>
  <TodosListComponent/>
</div>

Wir wollen den Input nach innen sich selbst zu deaktivieren, während der Laden geöffnet ist istSparen Zustand. Dies geschieht durch Abhören eines Änderungsereignisses im Geschäft. Darüber hinaus wollen wir auch unsere um sich selbst zu aktualisieren, wenn es Änderungen am Todos-Array gibt, und wir hören natürlich auf dasselbe Änderungsereignis, um dies zu erreichen. Was also passiert, ist Folgendes:

  1. Wir schnappen uns beide istSparen und alle wenn Komponenten erstellt werden
  2. Wir fügen eine neue Aufgabe hinzu, die ein „Änderungs“-Ereignis verursacht
  3. Das schnappt sich das Neue istSparen Zustand, sich selbst deaktivieren, und die packt den mutierten alle state, um die neue Aufgabe in der Liste anzuzeigen
  4. Wenn die asynchrone Operation abgeschlossen ist, lösen wir ein neues Änderungsereignis aus, wodurch unsere beiden Komponenten wieder dieselben Zustände annehmen, obwohl die musste nicht wirklich, da es keine neuen Mutationen auf dem Todos-Array gab

Unnötiges Reagieren auf Zustandsänderungen ist jedoch nicht der einzige Preis, schauen wir uns etwas genauer an.

Reagieren Sie JS Cascading Renders

Ein wichtiges Detail von React JS, das oft übersehen wird, ist das Wie setState auf einer Komponente wirkt sich auf die verschachtelten Komponenten aus. Wenn Sie verwenden setState nicht nur die aktuelle Komponente wird gerendert, sondern auch alle verschachtelten Komponenten. Das heißt, wenn ein Änderungsereignis auf Ihrer Anwendungsstammkomponente überwacht wird und ein Änderungsereignis vom Speicher ausgelöst wird, führen alle Ihre Komponenten ein Rendering und ein Diff durch, um alle erforderlichen DOM-Operationen zu erzeugen. Stellen wir uns das vor:

[Cascading render]

               /---\
               | X | - Root component renders
               |---|
                 |
            /----|---\
            |        |
          /---\    /---\
          | X |    | X | - Nested components also renders
          |---|    |---|              

Aber wenn eine verschachtelte Komponente a setState es wirkt sich nicht auf übergeordnete Komponenten aus.

[Cascading render]

               |---|
               |   | - Root component does not render
               |---|
                 |
            /----|---\
            |        |
          /---\    /---\
          |   |    | X | - Nested component renders
          |---|    |---|          

Dies bedeutet tatsächlich, dass Sie damit davonkommen könnten, nur auf Änderungen in Stores in Ihrer Root-Komponente zu lauschen, einen setState auszulösen und dann einfach den Status direkt aus den Stores in den verschachtelten Komponenten zu holen. Ein Beispiel dafür können wir mit unserer TodoApp erstellen:

var TodoApp = React.createClass({
  componentWillMount: function () {
    AppStore.on('change', this.update);
  },
  componentWillUnMount: function () {
    AppStore.off('change', this.update);
  },
  update: function () {
    this.setState({}); 
  },
  render: function () {
    return (
      <div>
        <AddTodoComponent/>
        <TodosListComponent/>
      </div>
    );
  }
});

Diese Komponente hört nur auf ein allgemeines Änderungsereignis und löst ein Rendern aus. Wie oben erwähnt, wird dies bis zu den verschachtelten Komponenten kaskadieren, wenn wir also z. in unserer mach das:

var AddTodoComponent = React.createClass({
  render: function () {
    return (
      <form>
        <input type="text" disabled={AppStore.isSaving()}/>
      </form>
    );
  }
});

Das ist eigentlich alles, was wir brauchen, um mit dem deaktivierten Zustand umzugehen. Es besteht keine Notwendigkeit, auf Änderungen zu warten, da unsere Root-Komponente dies für uns erledigt.

Dies gibt Ihnen Vorhersagbarkeit beim Rendern Ihrer Anwendung, aber es gibt natürlich ein Gleichgewicht. Nehmen wir an, Sie hören bereits auf die meisten Änderungsereignisse in der Stammkomponente Ihrer Anwendung. In dieser Situation könnten Sie erwägen, zusätzliche verschachtelte Listener zu entfernen, da sie nur unnötige Renderings verursachen. Aber wenn man eine sehr flache Komponentenstruktur hat, die auf viele verschiedene Läden hört, wäre das natürlich keine so gute Idee. Lass uns ein bisschen tiefer tauchen.

Wiederholtes Rendern

Ein allgemeines Änderungsereignis, das Folgendes auslöst: setState auf einer Komponente bewirkt nicht nur ein kaskadiertes Rendern, sondern auch ein wiederholtes Rendern in Komponenten. Lassen Sie mich erklären:

[Repeated rendering]

               /---\
               |   | - Root component listens to change
               |---|
                 |
            /----|---\
            |        |
          /---\    /---\
          |   |    |   | - Nested components listens to change
          |---|    |---|           

Wenn nun ein Änderungsereignis eintritt, löst die Root-Komponente zuerst ein Rendering aus:

[Repeated rendering]

            /---\
            | X | - Root component reacts to change event and renders
            |---|
              |
         /----|---\
         |        |
       /---\    /---\
       | X |    | X | - Nested components render
       |---|    |---|           

Und danach werden sich die verschachtelten Komponenten tatsächlich wieder selbst rendern:

[Repeated rendering]

       /---\
       |   | -
       |---|
         |
    /----|---\
    |        |
  /---\    /---\
  | X |    | X | - Nested components react to change event and renders
  |---|    |---|         

Wenn nun tiefer verschachtelte Komponenten vorhanden wären, würde dies den gleichen Effekt verursachen, aber bei noch mehr wiederholtem Rendern verursacht jede verschachtelte Ebene ein zusätzliches Rendern.

Wenn dies neu für Sie ist, könnte Ihre Reaktion im Genre von “Huch” liegen, aber React JS ist extrem schnell und in einfachen Anwendungen ist dies überhaupt kein Problem. Aber wenn Sie wie ich sind, wollen Sie wissen, wie Ihr Code läuft, und jetzt tun Sie es. Schauen wir uns also einige Lösungen an, um dieses Verhalten zu optimieren.

Optimierung

Zuerst möchte ich mir einen Moment Zeit nehmen, um mir das anzusehen shouldComponentUpdate Methode. Dies wird verwendet, um das Verhalten des kaskadierenden Renderns zu steuern. Schauen wir uns noch einmal unsere Visualisierung an:

[Cascading render]

               /---\
               | X | - Root component renders
               |---|
                 |
            /----|---\
            |        |
          /---\    /---\
          | X |    | X | - Nested components also renders
          |---|    |---|           

Wenn unsere verschachtelten Komponenten die shouldComponentUpdate Methode und es kehrte zurück false:

var NestedComponent = React.createClass({
  shouldComponentUpdate: function () {
    return false;
  },
  render: function () {
    return (
      <div></div>
    );
  }
});

Das wäre das Ergebnis:

[Render cascading]

               /---\
               | X | - Root component renders
               |---|
                 |
            /----|---\
            |        |
          /---\    /---\
          |   |    |   | - Nested components do not render
          |---|    |---|  

Aber es ist mühsam, dies zu all Ihren Komponenten hinzuzufügen. Was Sie stattdessen tun könnten, ist ein Mixin von React-Addons hinzuzufügen, genannt PureRenderMixin. Dies wird den gleichen Effekt haben.

var NestedComponent = React.createClass({
  mixins: [React.addons.PureRenderMixin],
  render: function () {
    return (
      <div></div>
    );
  }
});

Dies verbessert sicherlich die Rendering-Leistung, aber dennoch wird jede einzelne Komponente, die auf eine Änderung hört, versuchen, ein neues Rendering durchzuführen, selbst wenn es eine Zustandsänderung gab, die der Komponente egal war. Darüber hinaus müssen Sie sicherstellen, dass alle Objekte oder Arrays, die auf Ihren Requisiten oder Status festgelegt sind, ihre Referenz ändern, wenn sie geändert werden. Dies liegt daran, dass PureRenderMixin nur eine oberflächliche Prüfung durchführt.

Vermeidung unnötiger Renderings

Ein Beispiel für die Steuerung von Rendervorgängen ist die Verwendung von mehr als dem einzelnen “Änderungs”-Ereignis. Flussmittel-Reaktion verwendet EventEmitter2, um Namespace-Ereignisse zuzulassen. Lassen Sie mich Ihnen nur ein Beispiel zeigen:

var TodosListComponent = React.createClass({
  mixins: [flux.RenderMixin],
  componentWillMount: function () {
    AppStore.on('todos.*', this.changeState);
  },
  componentWillUnmount: function () {
    AppStore.off('todos.*', this.changeState);
  },
  changeState: function () {
    this.setState({
      todos: AppStore.getTodos()
    });
  },
  render: function () {
    return (
      <ul>
        {this.state.todos.map(function (todo) {
          return <li>{todo.title}</li>
        })}
      </ul>
    );
  }
});

Die Flussmittel-Reaktion RenderMixin macht die gleiche Arbeit wie PureRenderMixinaber jetzt müssen Sie die React-Addons nicht einschließen, um die Vorteile zu nutzen.

Wie Sie sehen können, habe ich beim Abhören von Ereignissen einen Asterix-Platzhalter verwendet. Dies bedeutet eigentlich, dass der Store beim Hinzufügen einer Aufgabe das folgende Ereignis ausgibt: this.emit(‘todos.add’)und, this.emit(‘todos.remove’), beim Entfernen einer Aufgabe. Unsere TodosListComponent wird auf beide Ereignisse reagieren. Eine andere Komponente ist möglicherweise nur daran interessiert, eine Aufgabe hinzuzufügen, und versucht daher nur erneut zu rendern, wenn dieses bestimmte Ereignis eintritt. Dies erhöht natürlich die Komplexität Ihrer Anwendung, aber Sie erhalten mehr Kontrolle über das Rendern und können auch auf Zustandsübergänge reagieren, was bei herkömmlichem Flussmittel eine Herausforderung darstellt. Lassen Sie mich erklären.

Nehmen wir an, Sie haben einen Iframe, der von einer Komponente gesteuert wird, die jedes Mal aktualisiert werden sollte, wenn eine Aufgabe hinzugefügt wird … aus welchem ​​​​Grund auch immer. Wie würden Sie das mit einem „Change“-Ereignis handhaben? Sie müssten wahrscheinlich einen Verweis auf die Anzahl der Aufgaben innerhalb der Komponente führen und bei allen Änderungsereignissen müssten Sie die in der Komponente gespeicherte Länge mit der Länge aus dem Speicher überprüfen. Das ist kein guter Weg, es zu tun. Wenn Sie ein “todos.add”-Ereignis ausgeben, wissen Sie jedoch, dass tatsächlich eine Aufgabe hinzugefügt wurde, und Sie können den Iframe bei jedem Auftreten dieses Ereignisses sicher aktualisieren.

Das Abhören bestimmter Zustandsübergänge ist etwas, das in modernen Single-Page-Anwendungen erforderlich ist. Routenänderungen, Animationen und andere Arten von Übergängen sind sehr schwierig mit einem einzigen “Änderungs”-Ereignis zu handhaben.

Staatsbaum

Es ist auch möglich, einen Statusbaum zu verwenden, z Baobab. Ich persönlich denke, dass dies die nächste Evolution der Flux-Architektur ist. Es löst so viele Herausforderungen und behält eine erstaunlich einfache API bei. Schauen wir uns zuerst ein Beispiel an:

var Baobab = require('baobab');
var stateTree = new Baobab({
  todos: []
}, {

  
  
  mixins: [React.addons.PureRenderMixin],

  
  
  
  shiftReferences: true
});

var todosCursor = stateTree.select('todos');
var TodosListComponent = React.createClass({

  
  
  
  mixins: [todosCursor.mixin],

  
  
  render: function () {
    return (
      <ul>
        {this.state.cursor.map(function (todo) {
          return <li>{todo.title}</li>
        })}
      </ul>
    );
  }
});

Mit Baobab können Sie einen Zustandsbaum erstellen, auf den Sie mit Cursorn zeigen können. Sie können Änderungen an diesen Cursorn anhören, wie wir im obigen Beispiel Änderungen an “todos” anhören. Dies ergibt eine sehr einfache API mit sehr wenig Boilerplate, die die Optimierung für Sie übernimmt. Baobab hat ein paar andere Tricks im Ärmel, die sich der Flux-Architektur anpassen, also ermutige ich Sie, es sich anzusehen.

Zusammenfassung

Dieser Artikel hat nicht die Absicht, Sie auf eine „Best Practice“ hinzuweisen. Es soll Ihnen einen Einblick geben, wie das React JS-Rendering funktioniert und wie sich die von Ihnen gewählte Flux-Implementierung auf das React JS-Rendering auswirkt. Abschließend möchte ich nur sagen, dass ich hoffe, dass dieser Artikel zu einem besseren Verständnis der Funktionsweise von React JS beigetragen hat. Flux ist immer noch ein sehr neues Konzept und wir werden wahrscheinlich für einige Zeit noch mehr Implementierungen sehen, aber React JS ist definitiv hier, um zu bleiben.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *