Statemangement im großen Stil: Redux vs. MobX

Sobald eine Single-Page-Application (SPA) richtig groß wird, ist ein gutes Statemanagement gefragt. Der State umfasst sämtliche Daten, auf die die einzelnen Bestandteile der SPA zugreifen. Die einzelnen Bestandteile sind teilweise simple Komponenten um die Daten des States darzustellen. Dies ist relativ einfach, allerdings müssen sie vom Browser automatisch geändert werden, sobald sich die Datenbasis ändert. D.h. das Statemanagement muss sich um einen Änderungsimpuls kümmern und eine Aktualisierung gewährleisten. Schwieriger für das Statemanagement ist es, den Komponenten Änderungen zu ermöglichen. Neben einfachen Änderungen der States wie z.B. durch TextInputs gibt es dazu auch noch asynchrone Requests. Dazu kommt eine gute Test- und Wartbarkeit, sowie Möglichkeiten zum Logging und Debugging. Allesamt recht hohe Anforderungen, die von Redux oder MobX in React und Vue.js übernommen werden können. Wir haben beide unter die Lupe genommen und stellen hier unser Ergebnis vor.

Redux – Der Industriestandard

Redux wird hauptsächlich von der breiten React Community präferiert. Das ist wohl auch der Grund, warum das Vue pardon vuex auch recht bekannt ist. Insgesammt ist Redux weit mehr als doppelt so bekannt wie MobX, vergleicht man die Stars auf GitHub.

Redux setzt die Flux Architektur (https://github.com/facebook/flux/tree/master/examples/flux-concepts) perfekt um. Sie beschreibt einen Datenfluss, der nur eine Richtung kennt. Anders als beim Two-Way-Binding, das z.B. Angular einsetzt, können bei Flux die Daten nur durch eine ausgelöste Action nur indirekt über den Dispatcher ändern. Sobald der Store eine Action empfängt, kann er seine Datenbasis anpassen und an die View bzw. Komponenten weitergeben. Die View kann nun wieder Actions auslösen und es ergibt sich somit ein Kreislauf.

Im nachfolgendem Code-Snippet zeigen wir eine Action, die z.B. von einem Button ausgelöst wird, um ein Todo zu einer Todo-Liste hinzuzufügen:

export const addTodo = text => ({
 type: 'ADD_TODO',
 id: nextTodoId++,
 text
})

Diese Action wird nun von einem sogenannten Reducer empfangen, der den nächsten Store (hier: state) zurückgibt:

const todos = (state = [], action) => {
 switch (action.type) {
   case 'ADD_TODO':
     return [
       ...state,
      {
         id: action.id,
         text: action.text,
         completed: false
      }
    ]
   default:
     return state
}
}

Zusätzlich ist noch ein wenig Implementierungsaufwand nötig, damit die React bzw. Vue Komponenten die addTodo Action tatsächlich verwenden kann. Außerdem müssen die Todos dem State verbunden werden, damit eine weitere Komponente diese anzeigen kann und sich bei Änderung aktualisiert. Das ausführliche Beispiel gibt es hier: https://redux.js.org/basics/exampletodolist

Nur schnell mal eben eine Todo-Liste zu implementieren artet mit Redux zu einem nicht unerheblichen Aufwand aus. Allerdings bietet die Flux Architektur dafür eine sehr gute Übersicht über den Datenfluss. Bei sehr großen Projekten zahlt sich der höhere Aufwand aus, da man die (zentralen) Änderungen am State sehr gut nachvollziehen kann. Ein weitere rießiger Vorteil ist die Abbildung des States als serialisierbares JSON. Der Reducer produziert nach jeder Änderung ein neues JSON-Objekt, das man problemlos loggen oder auch im Browser Storage speichern kann. Für letzteres gibt es redux-persist, die neben dem Speichern und Wiederhestellen des States auch Migrationen bei Versionsänderungen regelt.

MobX – Cooler aber weniger Funktionalität

MobX ist einfacher zu verstehen und hat deutlich weniger Overhead. Während Redux das Flux Pattern implementiert, wurde mit MobX das Observer Pattern umgesetzt. Ein weiterer großer Unterschied zu Redux ist zudem, dass der State kein immutable JSON-Object ist, sondern der State ein Singleton darstellt, welches direkt änderbar ist. Dafür dürfen es in MobX auch mal mehrere States sein.

Im folgenden Schnipsel ist z.B. der TodoStore aufgeführt:

class ObservableTodoStore {
@observable todos = [];
   @observable pendingRequests = 0;

   constructor() {
       mobx.autorun(() => console.log(this.report));
  }

@computed get completedTodosCount() {
  return this.todos.filter(
todo => todo.completed === true
).length;
  }

addTodo(task) {
this.todos.push({
task: task,
completed: false,
assignee: null
});
}
}

Alleine durch die Annotations kann man die Funktionalität schon gut erkennen. Durch @observalbe werden die zu beobachtenden Daten gekennzeichnet. Eine Komponente in React oder Vue muss sich nur noch mit der Annotation @observer ausstatten und schon ist das Observer-Pattern fertig. Allerdings ist die “Big-Magic” auch gleichzeitig der große Nachteil, da man anders als bei Flux schneller den Überblick verliert. Richtig schön ist bei MobX jedoch der Objektorientierte Ansatz und der wenige Overhead durch die Annotations. Wer mehr Funktionalität braucht, sollte sich mobx-state-tree anschauen. Diese Erweiterung bietet Schnittstellen für Middlewares, Snapeshots und vieles mehr. An den Umfang von Redux kommt es aber m.M.n. nicht ran.

Fazit

Wir bei innFactory haben die meisten Projekte mit Redux umgesetzt. Die vielen Addons und vorallem redux-persist entschädigen den doch recht nervigen Overhead. Die etwas höhere Einstiegshürde wird durch die Erfahrungen im Team abgefangen. Als sehr hilfreich hat sich unser Haus eigenes Boilerplate erwiesen, das gerne für neue Projekte verwendet werden darf. Wir nutzen es als Basis für zwei produktive Projekte: https://github.com/innFactory/create-react-app-material-typescript-redux

Comments

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.