Este proyecto tiene como objetivo aliviar el dolor en el desarrollo de SPA mediante la implementación de un patrón opinado para modelar el estado de una aplicación de una manera independiente del marco, fácil de probar y amigable para los desarrolladores. Sparix le permite encapsular el estado en Tiendas con seguridad de tipos y definir las transformaciones que pueden ocurrir en dicho estado. Una actualización nunca puede mutar el estado, sino que crea una nueva instancia transformada del estado (como un reductor redux). La secuencia de estados transformados se pone a disposición del público como RxJS Observable<State>
.
Sparix está escrito en TypeScript, al igual que los ejemplos de código. Se distribuye como una biblioteca JavaScript con definiciones de tipos integradas en el módulo NPM, al igual que 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 )
Primero, es un patrón. En segundo lugar, es una implementación basada en RxJS. La implementación es bastante trivial y solo tomaría un par de horas reescribirla con otra biblioteca reactiva. Sin embargo, dado que el mundo SPA estará dominado por React y Angular2, y dado que este último viene con RxJS, tenía sentido usar esta biblioteca para la implementación de referencia de sparix.
En sparix, el estado se modela como Observable<State>
, un flujo inmutable de transiciones de estado.
La API de una tienda se mantiene simple y toda la lógica compleja está encapsulada y oculta desde el exterior, tal como lo haría con la antigua programación orientada a objetos. Para probar una Tienda, todo lo que necesitas hacer es simular una entrada (llamando a uno de sus métodos públicos) y verificar su salida (el estado).
Sparix se adhiere completamente al principio redux (o más bien, al principio de Elm Architecture) donde las transformaciones de estado se definen como funciones puras que no mutan el estado anterior.
En redux, cuando necesitas actualizar el estado, envías una acción. Pero si miras de cerca, te darás cuenta de que las acciones se pueden clasificar en dos categorías:
Mi afirmación es que las acciones son un mecanismo demasiado pesado cuando el objetivo es simplemente actualizar el estado de una única Tienda (como en la mayoría de los casos). En Sparix, una Tienda puede actualizar directamente su estado sin más ceremonia que:
// Increment counter
this . update ( state => ( {
counter : state . counter + 1
} ) )
Existe una forma más detallada y declarativa de escribir estos actualizadores de estado:
this . updateState ( {
counter : prevCounter => prevCounter + 1
} )
O incluso mejor:
const increment = value => value + 1
this . updateState ( {
counter : increment
} )
Bueno, en realidad deberías aprovechar el curry automático de Ramda:
import { add } from 'ramda'
this . updateState ( {
counter : add ( 1 )
} )
Me gusta pensar en estos actualizadores de estado como acciones anónimas. En redux, sería como enviar un reductor. Pero ¿qué pasa con los creadores de acción? Bueno, realmente no los necesitamos:
const increment = val => val + 1
class SomeStore extends Store < SomeState > {
// constructor
incrementCounter ( ) {
this . updateState ( {
counter : increment
} )
}
}
Aquí, el método incrementCounter()
es parte de la API pública de la Tienda. Ya no es necesario enviar una acción global creada por un creador de acciones. ¡Simplemente llama al método!
Sparix se trata de modelar el núcleo de su aplicación. Pero ¿qué es un núcleo? O mejor dicho, ¿qué NO está en el núcleo?
El núcleo de la aplicación debe ser agnóstico. Agnóstico a los marcos y bases de datos, agnóstico a la capa de presentación, agnóstico al mecanismo y protocolos de recuperación de datos. Debería ser agnóstico A TODO .
El núcleo de la aplicación no conoce HTML, DOM, Angular o React, almacenamiento local, HTTP o WebSockets. ¡Ni siquiera sabe que reside en un navegador web! ¡El mismo núcleo de aplicación debería poder reutilizarse en aplicaciones Cordova, NativeScript o Electron sin cambiar una sola línea de código !
Entonces, ¿qué pones en el núcleo? La respuesta es bastante sencilla: ¡todo lo demás ! Si puede ser parte del núcleo, debería ser parte del núcleo. Toda la lógica empresarial, las transformaciones de datos y la lógica de interacción deben modelarse como el núcleo de la aplicación. Y nada de eso debería depender de nada más que del lenguaje de programación que se utilizó para modelarlo.
Así que volvamos a sparix. Le ayudará a modelar el núcleo de una aplicación que no depende de bibliotecas ni marcos de terceros, con dos excepciones: RxJS y el propio Sparix. Pero eso no es un gran problema. Los observables están en camino de convertirse en una característica estándar de ECMAScript, y sparix es una biblioteca no intrusiva, lo que facilita modelar solo un subconjunto del núcleo de su aplicación con ella.