oxjs
OxJS is a working in progress library written in TS that enable encapsulated reactivity, distantly inspired by VueJS reactivity system.
The library is compiled into UMD format and uses ES6 Proxies.
$ npm i -S oxjs
- oxjs bases
- reactive primitives
- reactive objects
- mixed observers
- nested observables props
- reactive arrays
- observable observers
- observerByProps
- tips for TS devs
- tests
- issues
oxjs bases
ox.observable<T extends object>(obj: T): T
This method takes an object or an array and transform it into an observable, so its fields can be used inside an evaluator to be observed (more on evaluators later):
const ox = ; const $observable = ox; // or const obj = a: 5 ;const $observable = ox;
In the latter case, be sure to apply changes on $observable
from this point on or observers won't be notified:
obja = newValue; // WRONG $observablea = newValue; // NICE
ox.observer<T extends Evaluator>(evaluator: T): ReturnType<T>
This method takes a function, called evaluator, and uses it to produce an observer.
Inside the evaluator, observables' fields will be used, and a value should be retured as the result of the observation process.
const ox = ; const $observable = ox; const sumObserver = ox;
The sumObserver
observer is called reactive primitive because it will act as a number
. In particular its value will be always the sum of the first three fields of the $observable
array, also when one of them will change.
That is because those fileds are accessed into the evaluator, so each time one of them changes, the evaluator will be called updating the sumObserver
value:
console; // 6 // update the $observable$observable0 = 5; console; // 10
reactive primitives
OxJS let you create special reactive objects that act like a primitive:
const ox = ; // it creates two observable sources from two object literalsconst $source1 = ox; const $source2 = ox; // it creates an observer that will behave as a stringconst stringObserver = ox; // WARNINGtypeof stringObserver; // object // initial evaluation// interpolation is used because 'stringObserver' is not a real string primitive, it behaves like a String object// so you need to use interpolation, or to call methods like valueOf(), toString()console; // My name is Mario and I'm 32 years old // we change the stored 'years' inside $source1$source1years = 28; // the 'stringObserver' is updatedconsole; // My name is Mario and I'm 28 years old // we change the stored 'name' inside $source2$source2name = 'Luigi'; // the 'stringObserver' is updatedconsole; // My name is Luigi and I'm 28 years old // because 'stringObserver' behaves like a String object, you can use it// almost everywhere a string is expected and you have access to all of String.prototype methodsstringObserver + "!!!"; // My name is Luigi and I'm 28 years old!!!stringObserver; // MY NAME IS LUIGI AND I'M 28 YEARS OLD!!! // WARNING HERE: 'stringObserver' is a String object and objects are truthy valuesifstringObserver // the condition is always true// you have to extract its value implicitly or explicitlyifstringObserver != "" // coercion :3ifstringObserver // same for number and boolean observersconst numberObserver = ox;const booleanObserver = ox; // WARNINGtypeof numberObserver; // objecttypeof booleanObserver; // object console; // 280console; // true // we change the stored 'years' inside $source1$source1years = 100; console; // 1000console; // false numberObserver; // 1.00e+3 // WARNING HERE: 'booleanObserver' is a Boolean object and objects are truthy valuesbooleanObserver && true; // true// you have to extract its valuebooleanObserver && true; // false // WARNING HERE: 'numberObserver' is a Number object and objects are truthy valuesifnumberObserver // the condition is always true// you have to extract its value implicitly or explicitlyifnumberObserver != 0 // coercion, again :)ifnumberObserver
reactive objects
Obviously we are not limited to primitives:
const ox = ; // it creates two observable sources from two object literalsconst $source1 = ox; const $source2 = ox; // it creates a reactive objectconst reactiveObject = ox; // initial evaluationconsole; // { years: 32, identity: { name: 'Mario' } } // we change the stored 'years' inside $source1$source1years = 28; // the 'reactiveObject' is updatedconsole; // { years: 28, identity: { name: 'Mario' } } // we change the stored 'name' inside $source2$source2name = 'Luigi'; // the 'reactiveObject' is updatedconsole; // { years: 28, identity: { name: 'Luigi' } }
mixed observers
You can mix primitives and objects observers, if you ever find a valid reason to do it:
const ox = ; // it creates two observable sources from two object literalsconst $source1 = ox; const $source2 = ox; const reactiveMess = ox; // initial evaluationconsole; // "32 years aren't enough" // update years$source1years = 50; console; // { years: 50, identity: { name: 'Mario' } }
nested observables props
Observables could have nested props:
const ox = ; // it creates an observable source from an object literal with nested propertiesconst $source = ox; // the observer will have three reactiver propsconst observer = ox; // see how encapsulated reactivity works; // after three seconds the '$source.nested' parent reference will be changed// but also nested observables will update accordingly;
reactive arrays
OxJS is pretty good with arrays as well:
const ox = ; const $source = ox; const sum = ox; console; // reduce result is: 6 $source3 = 4; console; // reduce result is: 10 $source console; // reduce result is: 20 $source console; // reduce result is: 19
observable observers
Observers can be used as observables, increasing the power in your hands.
Example with objects:
const ox = ; const $source = ox; // { foo: 84 }const $doubleSource = ox; // 83const doubledFooMinusOne = ox; // "doubledFooMinusOne is 83"console; $sourcefoo = 10; // $doubleSource: { foo: 20 } // "doubledFooMinusOne is 19"console;
Here $doubleSource is both an observer, because it does observe $source.foo doubling its value as result, and an observable, used by another observer: doubledFooMinusOne.
Example with arrays:
const ox = ; const $source = ox; // [2, 4, 6]const $doubleMappedSource = ox; // 12const sum = ox;// 3const length = ox; // 12 - 3console; $source; // $source: [1, 2, 3, 4], $doubleMappedSource: [2, 4, 6, 8] // 20 - 4console;
Here $doubleMappedSource is both an observer, because it does observe $source doubling its values as result, and an observable, used by both sum and length observers.
observerByProps
The observer
method is very powerful, because let you return an observable of any kind. But when it comes to create a reactive object, each time an observable source on which it depends changes, the whole observer is recreated from scratch.
This could constitute performance problems when heavy reactive objects are needed.
Thankfully OxJS provides an API to let you specify a separate observer for each reactive property:
const ox = ; // it creates an observable source from an object literal with nested propertiesconst $source = ox; // the observer will have three reactive propsconst observer = ox; // see how encapsulated reactivity works; // after three seconds the '$source.nested' parent reference will be changed// but also nested observables will update accordingly;
Currenlty nested props into the observer
are not supported. You cannot use Symbols as keys.
tips for TS devs
OxJS is written in TS and it's able to mantain types for observables and is able to extract types from the array of key
-executor
pair for observers created with observerByProps
.
For the latter though TS needs a little help.
observable
const $source1 = ox; // typeof $source1 is { years: number }
observer created with observerByProps
You have to pass an array as narrow as possible (from a type point of view) to correctly exctract type info.
const observer = ox
observer created with observer
Observers created with observe
deserve a separate discussion.
If a reactive object is generated, TS will infer the correct type:
const observer = ox; // typeof observer is { doubleValue: number }
If a reactive primitive is generated, TS will infer the primitive type:
const observer = ox); // typeof observer is number, not Number
tests
Incoming...
issues
Creating reactive primitives was a mess for me, for Typescript and for ES6 proxies. Have you ever seen a proxy with a dynamic target?
So if something does explode, please be patient and open a polite issue.