Целью этого проекта является облегчение разработки SPA за счет реализации проверенного шаблона для моделирования состояния приложения не зависящим от платформы, простым в тестировании и удобным для разработчиков способом. Sparix позволяет инкапсулировать состояние в типобезопасные хранилища и определять преобразования, которые могут произойти в этом состоянии. Обновление никогда не может изменить состояние, вместо этого оно создает новый преобразованный экземпляр состояния (точно так же, как редюсер redux). Преобразованная последовательность состояний становится общедоступной как RxJS Observable<State>
.
Sparix написан на TypeScript, как и примеры кода. Он распространяется как библиотека JavaScript с определениями типов, встроенными в модуль NPM, как и 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 )
Во-первых, это шаблон. Во-вторых, это реализация на основе RxJS. Реализация довольно тривиальна, и ее переписывание с помощью другой реактивной библиотеки займет всего пару часов. Однако, поскольку в мире SPA будут доминировать React и Angular2, а последний поставляется с RxJS, имело смысл использовать эту библиотеку для эталонной реализации sparix.
В sparix состояние моделируется как Observable<State>
, неизменяемый поток переходов состояний.
API Магазина остается простым, а вся сложная логика инкапсулирована и скрыта снаружи, как и в старом добром объектно-ориентированном программировании. Чтобы протестировать Store, все, что вам нужно сделать, это смоделировать входные данные (путем вызова одного из его общедоступных методов) и проверить его выходные данные (состояние).
Sparix полностью придерживается принципа редукции (или, скорее, принципа архитектуры Elm), где преобразования состояний определяются как чистые функции, которые не изменяют предыдущее состояние.
В Redux, когда вам нужно обновить состояние, вы отправляете действие. Но если присмотреться, то можно заметить, что действия можно разделить на две категории:
Я утверждаю, что действия — это слишком сложный механизм, когда целью является просто обновление состояния одного хранилища (как в большинстве случаев). В sparix Store может напрямую обновлять свое состояние без каких-либо церемоний, кроме:
// Increment counter
this . update ( state => ( {
counter : state . counter + 1
} ) )
Существует более детальный и более декларативный способ написания средств обновления состояния:
this . updateState ( {
counter : prevCounter => prevCounter + 1
} )
Или еще лучше:
const increment = value => value + 1
this . updateState ( {
counter : increment
} )
Ну, на самом деле вам следует использовать автоматическое каррирование Ramda:
import { add } from 'ramda'
this . updateState ( {
counter : add ( 1 )
} )
Мне нравится думать об этих средствах обновления состояния как об анонимных действиях. В Redux это было бы похоже на отправку редуктора. А как насчет создателей действий? Ну, они нам особо и не нужны:
const increment = val => val + 1
class SomeStore extends Store < SomeState > {
// constructor
incrementCounter ( ) {
this . updateState ( {
counter : increment
} )
}
}
Здесь incrementCounter()
является частью общедоступного API Магазина. Вам больше не нужно отправлять глобальное действие, созданное создателем действия. Просто вызовите метод!
Sparix занимается моделированием ядра вашего приложения. Но что такое ядро? Вернее, чего НЕТ в ядре?
Ядро приложения должно быть независимым. Независимость от фреймворков и баз данных, независимость от уровня представления, независимость от механизма получения данных и протоколов. Он должен быть агностиком ко ВСЕМ .
Ядро приложения не знает ничего о HTML, DOM, Angular или React, локальном хранилище, HTTP или WebSockets. Оно даже не знает, что оно живет в веб-браузере! Одно и то же ядро приложения должно быть повторно использовано в приложениях Cordova, NativeScript или Electron без изменения ни одной строки кода !
Так что же положить в ядро? Ответ довольно простой: все остальное ! Если это может быть частью ядра, оно должно быть частью ядра. Вся бизнес-логика, преобразования данных, логика взаимодействия должны моделироваться как ядро приложения. И ничто из этого не должно зависеть ни от чего, кроме языка программирования, который использовался для его моделирования.
Итак, вернемся к Спариксу. Он поможет вам смоделировать ядро приложения, которое не зависит от сторонних библиотек и фреймворков, за двумя исключениями — RxJS и сам sparix. Но это не большая проблема. Observables находятся на пути к тому, чтобы стать стандартной функцией ECMAScript, а sparix — это неинтрузивная библиотека, которая позволяет легко моделировать с ее помощью только часть ядра вашего приложения.