rstore
a straightforward, explicit, declarative and composable reactive store/model
Introduction
RStore is an observable (reactive) model.
Reactive?
Active | Reactive |
---|---|
direct query at some point in time | a subscription to all updates |
console.log(state.getValue()) | state.subscribe(value => console.log(value)) |
In the reactive world of observables you don't have to think if something has happened by the time something else happens. So you can concentrate on a single thing - defining a way the changes combine and map to the result.
What are the benefits?
- a state is defined in one place
store(0)
- every observer immediately receives a notification on a state update (including the initial state)
store(0)
.subscribe(value => console.log(value))
- the state can only be updated by other observables, so it changes whenever source stream produce updates
- the way updates modify the state is defined in terms of (widely adopted redux) reducers (see the interoperability section)
store(0)
.plug(Rx.Observable.of(1), (x, y) => x + y)
.subscribe(value => console.log(value))
- observables (sources of updates) can be defined in many ways
- RxJS 5
- Bacon.js
- Most.js
- built-in factories:
- fromEvent(element: HTMLElement, EventName: String) => Observable of events
- interval(n: number, ...values) => Observable that emits values every n milliseconds
- address() => Observable that acts like an Event Bus (see counters example)
- custom observer that implements observable interface
- RStore stores can be combined
const s1 = store(1);
const s2 = store(2);
s1.plug(s2, (x, y) => x + y)
.subscribe(value => console.log(value)); // 3
- RStore stores can be converted to RxJS Observables
store(1)
.toRx()
.map(x => x* 2)
.subscribe(state => console.log(state));
Example:
1 ) describe a model (properties, initial state):
const store = ; const myStore = ;
2 ) define sources of changes (using fromEvent
helper or Rx, Bacon, Most.js streams, see defining inputs)
const inc$ = ;const dec$ = ;
3 ) define how these changes affect the model:
myStore ;
4 ) subscribe to the store and get an updated model on every change:
myStore;
API
Creating stores
As RStore supports multiple kinds of observables (APIs), it exports 5 store constructors:
constructor | description |
---|---|
store | general, with an autodetect of the observable API |
storeR | RStore observables |
storeBacon | Bacon.js |
storeMost | Most.js |
storeRx | RxJS5 |
A constructor is a function that expects a single argument - an initial model value: It could be a primitive value
;
or an object
;
RStore methods
method | description |
---|---|
.plug(oObservable, fnReducer) |
subscribes RStore to the oObservable and for every value calls the fnReducer |
.unplug(oObservable, fnReducer) |
given oObservable and optionally fnReducer unsubscribes RStore from an external observer |
.subscribe(fnObserver) |
subscribes to RStore - calls fnObserver for every state update, including an initial value |
.reset() |
unsubscribes everything |
.toRx(Rx) |
converts a store to an Rx Observable |
Interoperability
RxJS5
Observables are sources of changes
;
A store can be converted to an observable
store(2)
.toRx(Rx) // Rx object, optional if Rx is global
.map(x => x * 2)
.subscribe(v => console.log(v)); // 4
Redux
Reducers can be reused
const observableOfChanges = RxObservable; const reduxReducer = { }; ; // 2
Redux store as a source of updates
const changes = type: 'INCREMENT' type: 'INCREMENT' type: 'DECREMENT' type: 'INCREMENT'; { } const reduxStore = Redux; changes; rstore ; // 4 changes;
Composability
As stores are Observables, they can be used as sources of changes:
// store 1 (counter from the previous example)const counter1 = ;const inc1$ = ;const dec1$ = ;counter1 ; counter1; // store 2 (a second counter, similar to counter 1)const counter2 = ;const inc2$ = ;const dec2$ = ;counter2 ; counter2; // a combining store, stores current states from both counters // notifies subscribers if any of the counter values changes ;
Redux DevTools Extension
STK works with Redux Devtools Extension:
- Install the tools
- Connect one of the stores to the DevTools:
const initial = 0;const store = rstore;rstoredevtools;
easy access to model fields
In many cases model modification functions are just setters that take the current state and a new value as inputs and produce a new model. It might be hard to access\update deeply nested fields as it is desirable that functions stay pure and data stays immutable.
rstore has a lens
function that is a pair of a setter and a getter for the given field:
const lens = lens;const l = ;const o1 = a: 11;const o2 = a: 22; // returns a value of the field 'a' in the object 'o1' (11)console // returns a value of the field 'a' in the object 'o2' (22)console // returns a new object like 'o1', but the field 'a' is set to '33' // ({a: 33}), object 'o1' stays the sameconsole // returns a new object like 'o2', but the field 'a' is set to '44' // ({a: 44}), object 'o2' stays the sameconsole