Ce projet vise à alléger les difficultés liées au développement de SPA en implémentant un modèle reconnu pour modéliser l'état d'une application d'une manière indépendante du framework, facile à tester et conviviale pour les développeurs. Sparix vous permet d'encapsuler un état dans des magasins de type sécurisé et de définir les transformations qui peuvent se produire sur ledit état. Une mise à jour ne peut jamais muter l'état, mais crée une nouvelle instance transformée de l'état (tout comme un réducteur redux). La séquence d'états transformés est rendue publique en tant que RxJS Observable<State>
.
Sparix est écrit en TypeScript, tout comme les exemples de code. Il est distribué sous forme de bibliothèque JavaScript avec des définitions de types intégrées dans le module NPM, tout comme RxJS.
$ 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 )
Premièrement, c'est un modèle. Deuxièmement, c'est une implémentation basée sur RxJS. L'implémentation est assez triviale et cela ne prendrait que quelques heures pour la réécrire avec une autre bibliothèque réactive. Cependant, étant donné que le monde SPA doit être dominé par React et Angular2, et que ce dernier est livré avec RxJS, il était logique d'utiliser cette bibliothèque pour l'implémentation de référence de sparix.
Dans sparix, l'état est modélisé comme un Observable<State>
, un flux immuable de transitions d'état.
L'API d'un magasin reste simple et toute la logique complexe est encapsulée et cachée de l'extérieur, tout comme vous le feriez avec la bonne vieille programmation orientée objet. Pour tester un Store, il suffit de simuler une entrée (en appelant une de ses méthodes publiques) et de vérifier sa sortie (l'état).
Sparix adhère complètement au principe redux (ou plutôt au principe d'architecture Elm) où les transformations d'état sont définies comme des fonctions pures qui ne mutent pas l'état précédent.
Dans Redux, lorsque vous devez mettre à jour l'état, vous envoyez une action. Mais si vous regardez attentivement, vous réaliserez peut-être que les actions peuvent être classées en deux catégories :
Mon affirmation est que les actions constituent un mécanisme trop lourd lorsque l'objectif est simplement de mettre à jour l'état d'un seul magasin (comme dans la plupart des cas). Dans Sparix, un Store peut directement mettre à jour son état sans plus de cérémonie que :
// Increment counter
this . update ( state => ( {
counter : state . counter + 1
} ) )
Il existe une manière plus fine et plus déclarative d'écrire ces mises à jour d'état :
this . updateState ( {
counter : prevCounter => prevCounter + 1
} )
Ou encore mieux :
const increment = value => value + 1
this . updateState ( {
counter : increment
} )
Eh bien, en fait, vous devriez tirer parti du curry automatique de Ramda :
import { add } from 'ramda'
this . updateState ( {
counter : add ( 1 )
} )
J'aime considérer ces mises à jour d'état comme des actions anonymes. En redux, ce serait comme envoyer un réducteur. Mais qu’en est-il des créateurs d’action ? Eh bien, nous n'en avons pas vraiment besoin :
const increment = val => val + 1
class SomeStore extends Store < SomeState > {
// constructor
incrementCounter ( ) {
this . updateState ( {
counter : increment
} )
}
}
Ici, la méthode incrementCounter()
fait partie de l'API publique du Store. Vous n'avez plus besoin de dispatcher une action globale créée par un créateur d'action. Appelez simplement la méthode !
Sparix consiste à modéliser le cœur de votre application. Mais qu'est-ce qu'un noyau ? Ou plutôt, qu'est-ce qui n'est PAS dans le noyau ?
Le noyau de l’application doit être agnostique. Indépendant des frameworks et des bases de données, indépendant de la couche de présentation, indépendant du mécanisme et des protocoles de récupération de données. Il devrait être indépendant de TOUT .
Le cœur de l'application ne connaît pas HTML, le DOM, Angular ou React, Local Storage, HTTP ou WebSockets.. Il ne sait même pas qu'il vit dans un navigateur web ! Le même noyau d'application doit être réutilisable dans les applications Cordova, NativeScript ou Electron sans changer une seule ligne de code !
Alors qu'est-ce qu'on met dans le noyau ? La réponse est assez simple : tout le reste ! Si cela peut faire partie du noyau, il devrait en faire partie. Toute la logique métier, les transformations de données, la logique d'interaction, doivent être modélisées comme le cœur de l'application. Et rien de tout cela ne devrait dépendre d’autre chose que du langage de programmation utilisé pour le modéliser.
Revenons donc à Sparix. Il vous aidera à modéliser un cœur d'application qui ne dépend pas de bibliothèques et de frameworks tiers, à deux exceptions près : RxJS et sparix lui-même. Mais ce n'est pas vraiment un problème. Les observables sont en passe de devenir une fonctionnalité ECMAScript standard, et sparix est une bibliothèque non intrusive, ce qui permet de modéliser facilement uniquement un sous-ensemble du cœur de votre application avec.