Dieses Projekt zielt darauf ab, die Probleme bei der SPA-Entwicklung zu lindern, indem ein Meinungsmuster zur Modellierung des Status einer Anwendung auf Framework-unabhängige, einfach zu testende und entwicklerfreundliche Weise implementiert wird. Sparix ermöglicht es Ihnen, Zustände in typsicheren Stores zu kapseln und die Transformationen zu definieren, die an diesem Zustand stattfinden können. Ein Update kann den Status niemals ändern, sondern erstellt stattdessen eine neue transformierte Instanz des Status (genau wie ein Redux-Reduzierer). Die transformierte Zustandssequenz wird als RxJS Observable<State>
öffentlich verfügbar gemacht.
Sparix ist in TypeScript geschrieben, ebenso wie die Codebeispiele. Es wird wie RxJS als JavaScript-Bibliothek mit im NPM-Modul eingebetteten Typdefinitionen verteilt.
$ npm i -S sparix rxjs
import { Store } from 'sparix'
import { add } from 'ramda'
export interface CounterState {
count : number
}
const initialState : CounterState = {
count : 0
}
export class Counter extends Store < CounterState > {
constructor ( ) {
super ( initialState )
}
increment ( ) {
// ALL THESE ARE EQUIVALENT
this . update ( state => { count : state . count + 1 } )
this . updateState ( { count : val => val + 1 } )
this . updateState ( { count : add ( 1 ) } ) // Using Ramda's automatically curryied functions
}
}
import { Counter , CounterState } from './counter'
const counter = new Counter ( )
// Recommended way
const state$ : Observable < CounterState > = counter . state$
const count$ : Observable < number > = counter . map ( state => state . count )
// Alternative way (useful for testing)
expect ( counter . currentState . count ) . toEqual ( 0 )
counter . increment ( )
expect ( counter . currentState . count ) . toEqual ( 1 )
Erstens ist es ein Muster. Zweitens handelt es sich um eine Implementierung, die auf RxJS basiert. Die Implementierung ist ziemlich trivial und es würde nur ein paar Stunden dauern, sie mit einer anderen reaktiven Bibliothek neu zu schreiben. Da die SPA-Welt jedoch von React und Angular2 dominiert werden soll und letzteres mit RxJS ausgeliefert wird, lag es nahe, diese Bibliothek für die Referenzimplementierung von Sparix zu verwenden.
In Sparix wird der Zustand als Observable<State>
modelliert, ein unveränderlicher Strom von Zustandsübergängen.
Die API eines Stores ist einfach gehalten und die gesamte komplexe Logik ist gekapselt und von außen verborgen, genau wie Sie es bei der guten alten objektorientierten Programmierung tun würden. Um einen Store zu testen, müssen Sie lediglich eine Eingabe simulieren (indem Sie eine seiner öffentlichen Methoden aufrufen) und seine Ausgabe (den Status) überprüfen.
Sparix hält sich vollständig an das Redux-Prinzip (oder besser gesagt das Elm-Architektur-Prinzip), bei dem Zustandstransformationen als reine Funktionen definiert werden, die den vorherigen Zustand nicht verändern.
Wenn Sie in Redux den Status aktualisieren müssen, lösen Sie eine Aktion aus. Aber wenn Sie genau hinschauen, werden Sie vielleicht erkennen, dass Aktionen in zwei Kategorien eingeteilt werden können:
Ich behaupte, dass Aktionen ein zu schwerer Mechanismus sind, wenn das Ziel lediglich darin besteht, den Status eines einzelnen Stores zu aktualisieren (wie in den meisten Fällen). In Sparix kann ein Store seinen Status direkt aktualisieren, ohne dass weitere Umstände erforderlich sind:
// Increment counter
this . update ( state => ( {
counter : state . counter + 1
} ) )
Es gibt eine detailliertere, deklarativere Möglichkeit, diese Statusaktualisierungsprogramme zu schreiben:
this . updateState ( {
counter : prevCounter => prevCounter + 1
} )
Oder noch besser:
const increment = value => value + 1
this . updateState ( {
counter : increment
} )
Nun, eigentlich sollten Sie Ramdas automatisches Currying nutzen:
import { add } from 'ramda'
this . updateState ( {
counter : add ( 1 )
} )
Ich stelle mir diese Zustandsaktualisierungen gerne als anonyme Aktionen vor. In Redux wäre es so, als würde man einen Reduzierer versenden. Aber was ist mit Action-Machern? Nun, wir brauchen sie nicht wirklich:
const increment = val => val + 1
class SomeStore extends Store < SomeState > {
// constructor
incrementCounter ( ) {
this . updateState ( {
counter : increment
} )
}
}
Hier ist die Methode incrementCounter()
Teil der öffentlichen API des Stores. Sie müssen keine globale Aktion mehr auslösen, die von einem Aktionsersteller erstellt wurde. Rufen Sie einfach die Methode auf!
Bei Sparix dreht sich alles um die Modellierung des Kerns Ihrer Anwendung. Aber was ist ein Kern? Oder besser gesagt, was ist NICHT im Kern?
Der Anwendungskern sollte agnostisch sein. Agnostisch gegenüber Frameworks und Datenbanken, agnostisch gegenüber der Präsentationsschicht, agnostisch gegenüber dem Datenabrufmechanismus und den Protokollen. Es sollte gegenüber ALLEM agnostisch sein.
Der Anwendungskern kennt weder HTML noch DOM, Angular oder React, Local Storage, HTTP oder WebSockets. Er weiß nicht einmal, dass er in einem Webbrowser läuft! Derselbe Anwendungskern sollte in Cordova-, NativeScript- oder Electron-Apps wiederverwendbar sein , ohne eine einzige Codezeile zu ändern !
Was steckt man also in den Kern? Die Antwort ist ganz einfach: Alles andere ! Wenn es Teil des Kerns sein kann, sollte es Teil des Kerns sein. Die gesamte Geschäftslogik, die Datentransformationen und die Interaktionslogik sollten als Anwendungskern modelliert werden. Und nichts davon sollte von etwas anderem abhängen als von der Programmiersprache, die zur Modellierung verwendet wurde.
Also zurück zu Sparix. Es hilft Ihnen, einen Anwendungskern zu modellieren, der nicht von Bibliotheken und Frameworks Dritter abhängt, mit zwei Ausnahmen: RxJS und Sparix selbst. Aber das ist kein großes Problem. Observables sind auf dem Weg, eine Standard-ECMAScript-Funktion zu werden, und Sparix ist eine nicht-intrusive Bibliothek, die es einfach macht, damit nur eine Teilmenge Ihres Anwendungskerns zu modellieren.