React Flux

written by devangelist on Januar 12, 2015 in Allgemein and Javascript and Patterns and React with one Comment

Flux ist eine Architektur, die Facebook für client-seitige Web Applikationen verwendet. Es ist also kein Framework oder Bibliothek die eingebunden werden kann, sondern viel mehr eine Ergänzung eines unidirektionalen Datenflusses zu den React View Komponenten.

Flux Applikationen bestehen aus mehreren Komponenten: Actions, Dispatcher, Stores und Views (die eigentlichen React Komponenten).

React Flux Architektur - unidirectional data flow

  • Actions: Hilfsmethoden um Daten an den Dispatcher zu transportieren. Beispiel: updateText(todoId, newText)
  • Dispatcher: Erhält Actions und broadcasted das Payload zu den registrierten Callbacks (Stores).
  • Stores: Enthalten den Application State und die Logik und haben Callbacks auf den Dispatcher registriert.
  • Controller Views: React Komponenten welche sich den state von den Stores holen und diesen als props den child-Komponenten weiterreichen.

Der Unterschied zu klassischen Patterns wie MVC (Model View Controller) ist liegt im Datenfluss. Wenn ein Benutzer mit der React View interagiert, so sendet diese eine Action durch den zentralen Dispatcher hin zu den verschiedenen Stores, welche den state und die business Logik beinhalten. Die Stores wiederum aktualisieren alle betroffenen Views.

Der View Schicht ist es nicht erlaubt den state direkt zu ändern – sie muss fire-and-forget Anweisungen an den Dispatcher senden. Mit diesem Weg haben Views keine andere Verantwortung als den aktuellen state der Anwendung zu rendern.

Ein fluxiges Beispiel bei Facebook

Ein gutes Beispiel von Facebook zeigt, warum dieser Weg sinnvoll ist:
Sendet jemand eine neue Nachricht, so erscheint in der Menüleiste ein rotes Bubble-Icon das auf nicht gelesene Nachrichten hinweist. Gleichzeitig erscheinen die Nachrichten auch im Chat-Fenster. Wird nun die Nachricht gelesen, so verschwindet das rote Hinweis-Icon.
Dieses Szenario in MVC abzubilden würde bedeuten, dass zum einen das Nachrichten-Model und das Nicht-gelesene-Nachrichten-Model aktualisiert werden müssten. Diese Abhängigkeiten sowie solche kaskadierenden Aktualisierungen sind keine Ausnahme bei großen Webanwendungen und führen oft zu komplexen Datenströmen und unvorhersehbaren Ergebnissen.

Mit Flux hingegen würde die View eine neue Action generieren und diese durch den zentralen Dispatcher senden. Die einzelnen darauf registrierten Stores können dann entscheiden, was sie mit dem Update machen (beispielsweise das Hinweis-Icon wieder auszublenden wenn alle Nachrichten gelesen wurden). Keine Komponente außerhalb des Stores weiß, wie dieser intern die Daten für seine Domäne verwaltet, was für eine klareres “Separation of Concerns” spricht.

Ein einzelner Dispatcher

In einer Flux Applikation ist der Dispatcher das zentrale Hub, welches den gesamten Datenfluss verwaltet. Er besitzt selbst keine großartige Logik, sondern erhält Actions aus verschiedenen Stellen (Benutzerinteraktion in den Views, Server/API, …) und verteilt diese auf die einzelnen Stores, welche sich mit einem Callback registriert haben.

Flux Dispatcher - Store Callbacks

Der Dispatcher kann die Abhängigkeiten zwischen den einzelnen Stores verwalten, indem er beispielsweise die registrierten Callbacks in einer gewissen Reihenfolge ausführt oder auf Updates wartet, bevor er fortfährt.

var Dispatcher = require('flux').Dispatcher;
var AppDispatcher = new Dispatcher();

AppDispatcher.handleViewAction = function(action) {
  this.dispatch({
    source: 'VIEW_ACTION',
    action: action
  });
}

module.exports = AppDispatcher;

Die handleViewAction-Methode ruft die dispatch Methode auf, welche den Action-Payload an alle registrierten Callbacks weitergibt. Die view Action Angabe ist sinnvoll um view von server oder API Actions zu unterscheiden.

Flux Stores

Die Aufgabe eines Flux Stores ist einem klassischen MVC Model ähnlich. Allerdings verwaltet ein Store nicht nur ein Model, wie das bei ORM Models der Fall ist, sondern den state von mehreren Objekten. Es soll aber auch keine Collection, wie das aus Backbone bekannt ist, sein, sondern viel mehr der application State für eine bestimmte Domäne innerhalb der Anwendung.

Wie bereits erwähnt, registriert sich ein Store mittels eines Callbacks eigenständig beim Dispatcher. Der Callback wiederum bekommt die Action als ein Parameter mit. Innerhalb dieses Callbacks wird mit einem switch-Statement auf den actionType abgefragt, welche interne Methoden ausgeführt werden:

var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TodoConstants = require('../constants/TodoConstants');
var assign = require('object-assign');
var _todos = {};

var TodoStore = assign({}, EventEmitter.prototype, {

  getAll: function() {
    return _todos;
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  }
}

AppDispatcher.register(function(action) {
  var text;

  switch(action.actionType) {
    case TodoConstants.LOAD_TODOS:
      // Call internal method like loadTodos...
      _todos = action.data;
      TodoStore.emitChange();
      break;

    // ...
  }
});

module.exports = TodoStore;

Wenn sich ein Store aktualisiert hat, wird mit einem Event eine Statusaktualisierung an die Views übertragen, sodass sich diese mit dem neuen state aktualisieren können.

Wichtig ist beim Beispiel, dass der Store von NodeJS’s EventEmitter erweitert wird, sodass die Stores auf Events hören oder Events senden können. Gleichzeitig können die View Komponenten auf diese Events lauschen und sich bei Änderungen aktualisieren und neu rendern.

Actions und Action Creators

Action Creators sind semantische Hilfsmethoden, welche eine Action zum Dispatcher senden. Um beispielsweise den Text eines ToDo Items zu ändern, kann eine action updateText(todoId, newText) erstellt werden:

var AppDispatcher = require('../dispatcher/AppDispatcher');
var TodoConstants = require('../constants/TodoConstants');
var TodoActions = {
  updateText: function(id, text) {
    AppDispatcher.dispatch({
      actionType: TodoConstants.TODO_UPDATE_TEXT,
      id: id,
      text: text
    });
  }
};
module.exports = TodoActions;

Action Creators werden in der Regel in den Eventhandlers der Views ausgeführt, um auf Benutzerinteraktionen zu reagieren. Aber auch wenn neue Daten vom Server gesendet werden, können diese hilfreich sein.

Abhängigkeiten mit waitFor

Wenn ein Teil der Anwendung von einem anderen abhängig ist, so lässt sich dies mit der waitFor Methode im Dispatcher Modul umsetzen. Allerdings muss erst noch der Rückgabewert der Registrierungsmethode im Store gesetzt werden:

PrependedTextStore.dispatchToken = Dispatcher.register(function (payload) {
  // ...
});

Anschließend kann im ToDo-Store die waitFor mit Angabe des dispatchTokes verwendet werden:

case 'TODO_CREATE':
  Dispatcher.waitFor([
    PrependedTextStore.dispatchToken,
    YetAnotherStore.dispatchToken
  ]);

Fazit

Zusammengefasst kann folgendes Diagramm den Datenfluss in Flux noch einmal erklären:

Facebook React/Flux Architektur

Die Flux Implementierung von Facebook ist nicht die einzige: Es gibt mittlerweile weitere wie Fluxxor, RefluxJS oder Yahoo’s fluxible-app Ansatz.

Wenn die erste Anwendung mit Flux erst einmal entwickelt ist, fühlt sich React ohne Flux gleich wie DOM Manipulationen in JavaScript ohne jQuery vor ein paar Jahren an ;-)

React Flux: 1 Stern2 Sterne3 Sterne4 Sterne5 Sterne 5,00 von 5 Punkte, 5 abgegebene Stimmen.
Loading ... Loading ...

About the Author

Roberto Bez ist passionierter Webentwickler und TechLead bei der HolidayCheck AG. Für Roberto bedeutet das Entwickeln nicht nur Arbeit, sondern auch Freude, Motivation und täglich neue, aufregende Herausforderungen. Besonders gerne setzt er sich mit neuen Webtechnologien sowie Datenbanken aller Art auseinander und versucht diese in die tägliche Anwendungsentwicklung miteinzubringen. Neben dem Entwickeln trifft man ihn gerne Abends beim Laufen oder im Sommer bei Mountainbike-Touren durch die schönen Berge Südtirols.