SubX is next generation state container. It could replace Redux and MobX in React apps.
Subject X, Reactive Subject. Pronunciation: [Sub X]
If you want to use SubX together with React, please check react-subx.
yarn add subx
import SubX from 'subx'
const person = SubX.create()
person.$.subscribe(console.log)
person.firstName = 'Tyler'
person.lastName = 'Long'
{ type: 'SET', path: ['firstName'], id: 'uuid-1' }
{ type: 'SET', path: ['lastName'], id: 'uuid-2' }
In the sample code above, person
is a SubX object. person.$
is a stream of events about changes to person
's properties.
If you know RxJS
, I would like to mention that person.$
is an Observable.
Subject is the similar concept as the subject in observer pattern.
A reactive subject is a special JavaScript object which allows us to subscribe to its events. If you are a React + Redux developer, events is similar to actions. If you are a Vue.js + Vuex developer, events is similar to mutations.
In content below, we call a reactive subject a SubX object.
It is easy to convert a SubX object to a plain object: const plainObj = subxObj.toObject()
.
Currently there are 5 basic events: SET
, DELETE
, GET
, HAS
& KEYS
.
The corresponding event streams are set$
, delete$
, get$
, has$
& keys$
There are 3 advanced events: COMPUTE_BEGIN
, COMPUTE_FINISH
& STALE
.
The corresponding event streams are compute_begin$
, compute_finish$
& stale$
.
set$
& $
Most of the event mentioned in this page are SET
events. SET
means a property has been assigned to. Such as person.firstName = 'John'
.
const person = SubX.create({ firstName: 'Tyler' })
person.set$.subscribe(console.log)
person.firstName = 'Peter'
$
is a synonym of set$
. We provide it as sugar since set$
is the mostly used event.
delete$
DELETE
events are triggered as well. We already see one of such event above in "Array events" section. Here is one more sample:
const person = SubX.create({ firstName: '' })
person.delete$.subscribe(console.log)
delete person.firstName
get$
GET
events are triggered when we access a property
const person = SubX.create({ firstName: '' })
person.get$.subscribe(console.log)
console.log(person.firstName)
has$
GET
events are triggered when we use the in
operator
const person = SubX.create({ firstName: '' })
person.has$.subscribe(console.log)
console.log('firstName' in person)
keys$
KEYS
events are triggered when we use Object.keys(...)
const person = SubX.create({ firstName: '' })
person.keys$.subscribe(console.log)
console.log(Object.keys(person))
compute_begin$
, compute_end$
& state$
These 3 events are advanced. Most likely we don't need to know them. They are for computed properties(which is covered below).
COMPUTE_BEGIN
is triggered when a computed property starts to compute.COMPUTE_FINISH
is triggered when a computed property finishes computing.STALE
is triggered when the computed property becomes "stale", which means a re-compute is necessary.We use "convention over configuration" here: getter functions are computed properties. If we don't need it to be computed property, just don't make it a getter function.
So in SubX, "computed properties" and "getters" are synonyms. We use them interchangeably.
const Person = SubX.model({
firstName: 'San',
lastName: 'Zhang',
get fullName () {
return `${this.firstName} ${this.lastName}`
}
})
const person = Person.create()
expect(person.fullName).toBe('San Zhang')
What is the different between computed property and a normal function? Computed property caches its results, it won't re-compute until necessary.
So in the example above, we can call person.fullName
multiple times but it will only compute once. It won't re-compute until we change either firstName
or lastName
and invoke person.fullName
again.
I would recommend using as many getters as we can if our data don't change much. Because they can cache data to improve performance dramatically.
Computed properties / getters are supposed to be "pure". We should not update data in them. If we want to update data, define a normal function instead of a getter function.
autoRun
The signature of autoRun
is
// autoRun :: (subx, f, ...operators) -> stream$
Method signature explained:
subx
is a SubX objectf
is an action/function...operators
are RxJS operatorsstream$
is a stream (RxJS Subject)autoRun
work:autoRun
, the second argument f
is invoked immediately.subx
is monitored.subx
changes which might affect the result of f
, f
is invoked again.f
is further controlled by ...operators
.f()
are directed to the returned stream$
stream$.subscribe(...)
to consume the results of f()
stream$.complete()
to stop the whole monitor & autoRun process described above.autoRun
runAndMonitor
runAndMonitor
is low level API which powers autoRun
. If for some reason autoRun
is not flexible enough to meet your requirements, you can give runAndMonitor
a try.
The signature of runAndMonitor
is:
// runAndMonitor :: subx, f -> { result, stream$ }
Method signature explained:
subx
is a SubX objectf
is an action/functionresult
is the result of f()
stream$
is a stream (RxJS Subject)runAndMonitor
work:runAndMonitor
, the second argument f
is invoked immediately.f()
is saved into result
subx
is monitored.subx
which might affect the result of next invocation of f
are redirected to stream$
{ result, stream$ }
is returnedstream$.pipe(...operators).subscribe(...)
to react to the stream events (possibly invoke f
again)runAndMonitor
By default, a SubX Object is recursive. Which means, all of its property objects are also SubX objects. For example:
const p = SubX.create({ a: {}, b: {} })
p
is a SubX object, so are p.a
and p.b
.
You can disable the recursive behavior:
const p1 = SubX.create({ a: {}, b: {} }, false)
const P = SubX.model({ a: {}, b: {} }, false)
const p2 = P.create()
p1
and p2
are SubX objects while none of p1.a
, p1.b
, p2.a
, p2.b
are SubX objects.
let p = SubX.create({ a: {}, b: {} })
p = SubX.create(p.toObject(), false)
let p = SubX.create({ a: {}, b: {} }, false)
p = SubX.create(p)
If we create circular data structure with SubX, the behavior is undefined. Please don't do that.
Please read the wiki. We have a couple of useful pages there.
Our test cases have lots of interesting ideas too.
Version | Tag | Published |
---|---|---|
0.10.1 | latest | 10mos ago |