reactive-tree
v4.0.0
Published
A simple library for reactive programming
Downloads
7
Maintainers
Readme
reactive-tree
A simple library for reactive programming.
Requires RxJS 6.
Installation
npm install reactive-tree
Definition
Leaves
A leaf defines a reactive state. It reacts whenever its value changes.
Example: Create a leaf
import { createLeaf } from "reactive-tree";
const leaf = createLeaf("world");
console.log(`Hello, ${leaf.value}.`); // Hello, world.
A second way to create a leaf is using defineLeaf()
.
This example doesn't show how a leaf reacts whenever its value changes. Let's keep reading.
Twigs
A twig defines a computed state. It has a value that is computed from its
handler
function. It computes only when someone try to get its value and it's
dirty.
A twig gets dirty when:
- it's just created, or
- it reads leaves (or other twigs) inside its
handler
function and any of those leaves (or other twigs) reacts, or - its
dirty
property is set to true.
A twig also behaves like a reactive state if, inside its handler
function, it
reads values from leaves (or other twigs).
Example: Create a twig
import { createLeaf, createTwig } from "reactive-tree";
const leaf = createLeaf("world");
const twig = createTwig(() => `Hello, ${leaf.read()}.`);
console.log(twig.value); // Hello, world.
leaf.write("kitty");
console.log(twig.value); // Hello, kitty.
Now you can see that this line leaf.write("kitty");
causes the leaf to react,
which causes the twig to become dirty, because the twig reads the leaf
inside its handler
function. As a result, the second twig.value
now has a
different value.
A second way to create a twig is using defineTwig()
.
Branches
A branch starts a reactive procedure: it collects reactive states by calling its
handler
function; then it waits until any of those reactive states reacts, it
schedules to restart this procedure (by default, using setTimeout
function).
Example: Create a branch
import { createBranch, createLeaf, createTwig } from "reactive-tree";
const leaf = createLeaf("world");
const twig = createTwig(() => `Hello, ${leaf.read()}.`);
createBranch(() => {
console.log(twig.read());
});
leaf.write("kitty");
// Output:
// Hello, world.
// Hello, kitty.
Basically, this example illustrates how a branch reacts whenever any of reactive
states inside its handler
function changes. Things you should know that:
createBranch()
immediately calls its sole argument, thehandler
function, which produces the first line of output;- the second line of output does not immediately show up, but the gap is too small to be noticed.
Branches can be nested inside each other.
Example: Nesting
import { createBranch, defineLeaf } from "reactive-tree";
class Book {
constructor(public name: string) {
defineLeaf(this, "name");
}
}
class MyApp {
showThisBook = null as Book | null;
constructor() {
defineLeaf(this, "showThisBook");
}
}
const myApp = new MyApp();
createBranch(() => {
const book = myApp.showThisBook;
if (book === null) {
console.log("No book is showing.");
return;
}
console.log("We are showing a book.");
createBranch(() => {
console.log(`Name of the book is "${book.name}".`);
});
});
setTimeout(() => {
const book = new Book("How To Make Cookies");
myApp.showThisBook = book;
setTimeout(() => {
book.name = "How To Plant A Tree";
setTimeout(() => {
myApp.showThisBook = null;
book.name = "This One Will Not Show Up";
}, 2000);
}, 2000);
}, 2000);
// Output:
// No book is showing.
// (2 seconds later)
// We are showing a book.
// Name of the book is "How To Make Cookies".
// (2 seconds later)
// Name of the book is "How To Plant A Tree".
// (2 seconds later)
// No book is showing.
API
class Leaf
function createLeaf<T>(value: T): Leaf<T>;
function defineLeaf<T, K extends keyof T>(
target: T,
propertyKey: K
): Leaf<T[K]>;
function defineLeaf<T>(
target: object,
propertyKey: string | symbol,
value: T
): Leaf<T>;
class Leaf<T> implements Signal {
static defaultSelector = distinctUntilChanged();
static create = createLeaf;
static define = defineLeaf;
value: T;
selector: OperatorFunction<T, T>;
readonly subject: Subject<T>;
read(): T;
write(value: T): void;
observe(observable: ObservableInput<T>): Subscription;
unobserve(): void;
}
function createLeaf()
Create a leaf with a value.
function defineLeaf()
Create a leaf with a value, like createLeaf()
, but also defines a property for
an object, which corresponds with this leaf:
- when you get this property, it returns
leaf.read()
; - when you set this property to something, it calls
leaf.write(something)
.
class Leaf: value
Get or set the value
.
Generally, you should consider using read()
or write()
instead of getting or
setting this property.
class Leaf: selector
Determine whether the leaf should react when a new value writes to it.
By default, leaves react only when a different value writes to them. You can change this behavior by setting this property.
Note that setting this property has no effect if the leaf has been read by twigs or branches. Setting this property just after the leaf is created is the recommended way.
class Leaf: subject
Get a Subject
for this leaf. Subsequent calls return the same one.
The subject responds to write()
and observe()
.
class Leaf: read()
read()
returns value
. Additionally, calling read()
inside a handler
function causes the leaf to be collected by the owner of that handler
function, which must be a twig or a branch.
class Leaf: write()
Set value
property to a new value. It also causes the leaf to react if this
new value differs from the old one (To change this behavior, see
selector
).
class Leaf: observe()
Subscribe an Observable
and returns a Subscription
. A write()
cancels this
subscription. Each value emitted by this observable is written to the leaf, like
write()
but without canceling this subscription.
class Leaf: unobserve()
Cancel all subscriptions created by observe()
. A write()
also cancels all
those subscriptions.
class Twig
function createTwig<T>(handler: () => T): Twig<T>;
function defineTwig<T>(
target: object,
propertyKey: string | symbol,
handler: () => T
): Twig<T>;
class Twig<T> implements Signal {
static create = createTwig;
static define = defineTwig;
dirty: boolean;
handler: () => T;
readonly value: T;
read(): T;
write(value: T): void;
clean(): void;
notify(): void;
connect(signal: Signal): void;
}
function createTwig()
Create a twig with a handler.
function defineTwig()
Create a twig with a handler, like createTwig()
, but also defines a property
for an object, which corresponds with this twig:
- when you get this property, it returns
twig.read()
. - when you set this property to something, it calls
twig.write(something)
.
class Twig: dirty
Indicate whether the twig should update the cached value.
class Twig: handler
Get or set the handler
.
If set, you may also want to set dirty
to true and make a call to notify()
.
class Twig: value
Get the cached value computed from handler
function.
If dirty
is true, a new value returned by handler
function will be cached
and used instead. And then dirty
is set to false.
Generally, you should consider using read()
instead of getting this property.
class Twig: read()
read()
returns value
. Additionally, calling read()
inside a handler
function causes the twig to be collected by the owner of that handler
function, which must be a twig or a branch.
class Twig: write()
By default, write()
throws an error. Overwrite this property if you need it.
class Twig: clean()
Clean the twig if it's dirty.
class Twig: notify()
Force the twig to react.
class Twig: connect()
Connect a signal with the twig.
connect()
should only be called inside the handler
function.
class Branch
function createBranch(handler?: (branch: Branch) => void): Branch;
function createBranch(
scheduler?: Scheduler,
handler?: (branch: Branch) => void
): Branch;
class Branch {
static create = createBranch;
handler?: (branch: Branch) => void;
scheduler?: Scheduler;
readonly stopped: boolean;
readonly disposed: boolean;
run(): void;
stop(): void;
dispose(): void;
freeze(): void;
unfreeze(): void;
schedule(): void;
unschedule(): void;
connect(signal: Signal): void;
teardown(x: TeardownLogic): void;
finalize(x: TeardownLogic): void;
}
function createBranch()
Create a branch with a handler and/or a scheduler.
If scheduler
is not specified, but the parent branch has one, that one will be
used. That is to say, inner branches share the same scheduler from their parent
branch, if their scheduler
are not set.
Schedulers are used to change the way how branches schedule when they react.
class Branch: handler
Get or set the handler
.
If set, you need to call run()
or schedule()
to take effect.
class Branch: scheduler
Get or set the scheduler
.
If not set, Scheduler.default
is used.
class Branch: stopped
Check if the branch stops.
class Branch: disposed
Check if the branch disposes.
class Branch: run()
Force the branch to restart its procedure immediately.
An error throws if run()
is called inside the handler
function.
class Branch: stop()
Stop the branch.
class Branch: dispose()
Dispose the branch.
You can run()
or schedule()
a stopped branch again, but not a disposed one.
class Branch: freeze()
Freeze the branch. Subsequent reactive states will NOT be collected by the branch.
freeze()
should only be called inside the handler
function.
class Branch: unfreeze()
Unfreeze the branch, ready to collect subsequent reactive states.
unfreeze()
should only be called inside the handler
function.
unfreeze()
need not be called if there is no subsequent reactive states after.
class Branch: schedule()
Make a schedule to run()
.
class Branch: unschedule()
Undo schedule()
.
class Branch: connect()
Connect a signal with the branch.
connect()
should only be called inside the handler
function.
class Branch: teardown()
Add something to do when the branch restarts or stops or disposes. This is
useful if you need to undo something that is done inside the handler
function.
teardown()
should only be called inside the handler
function.
class Branch: finalize()
Add something to do when the branch disposes.
finalize()
should only be called outside the handler
function.
class Scheduler
function createAsyncScheduler(schedule?: (cb: () => void) => void): Scheduler;
interface Scheduler {
schedule(branch: Branch): void;
unschedule(branch: Branch): void;
}
class Scheduler {
static createAsync = createAsyncScheduler;
static async: Scheduler;
static sync: Scheduler;
static default = Scheduler.async;
}
function createAsyncScheduler()
Create an async scheduler with a schedule function.
An async scheduler opens a window when scheduling a branch. During the lifetime
of the window, multiple branches will then be put together. When the window
closes, all these branches will then run()
one by one in
the order that oldest runs first and then the scheduler is ready to open another
window.
The schedule function defines how a window opens and when it closes. If not provided, a default one will be used.
interface Scheduler: schedule()
Make a schedule to run()
a branch.
interface Scheduler: unschedule()
Undo what schedule()
does to a branch.
class Scheduler: async
An async scheduler. See
createAsyncScheduler()
.
class Scheduler: sync
A scheduler. When scheduling a branch, sync
immediately
run()
s it.
class Scheduler: default
The default scheduler, which is async
.
class Signal
function createSignal(source: ObservableInput<any>): Signal;
function connectSignal(signal: Signal): void;
function collectSignals(cb: () => void): Signal[];
interface Signal {
readonly name?: string | symbol;
readonly identity: number;
readonly observable: Observable<Signal>;
}
class Signal {
static create = createSignal;
static connect = connectSignal;
static collect = collectSignals;
}
function createSignal()
Create a signal from an observable.
function connectSignal()
Connect a signal. connectSignal()
should only be called inside a handler
function (twigs' or branches'). Any emission from this signal causes those twigs
or branches to react (twigs become dirty, branches schedule to run again).
function collectSignals()
Collect signals inside the callback function, return an array of them.
interface Signal: name
The name of the signal.
For leaves that are created by defineLeaf()
or
reactive()
, their name
would be the value passed to
the function for the propertyKey
parameter.
For twigs that are created by defineTwig()
or
computed()
, their name
would be the value passed to
the function for the propertyKey
parameter.
interface Signal: identity
The identity for the signal.
This property is for uniqueness, for ordering, the value itself has no meaning.
interface Signal: observable
The observable provided by the signal. Each emission of this observable represents a signal.
Decorators
function reactive(target: object, propertyKey: string | symbol): void;
function computed(
target: object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
): PropertyDescriptor;
function reactive()
Add a get-setter property to a class. The first time you set a value to this property of an instance, a leaf is created and bound to it.
function computed()
Wrap an existing get-setter property of a class. The first time you get this property of an instance, a twig is created and bound to it.
Others
function when(predicate: () => boolean, effect: () => void): Branch;
function whenever<T>(
expression: () => T,
effect: (data: T, branch: Branch) => void,
selector?: OperatorFunction<T, T>,
fireImmediately?: boolean
): Branch;
function when()
When predicate
returns true, efffect
is called (and only called once).
To cancel when()
, dispose()
the returned branch. After effect
is called,
the returned branch will be disposed too.
function whenever()
Whenever expression
returns a value that differs from the old one, effect
is
called with the returned value and a branch that can be used to cancel
whenever()
as parameters.
whenever()
also returns the branch that passes to effect
. To cancel
whenever()
, dispose()
the branch.
selector
can be used to determine which values, returned by expression
,
should pass to effect
. By default, Leaf.defaultSelector
is used.
If fireImmediately
is true, effect
will be immediately called with the first
value returned by expression
and the branch mentioned above as parameters.
Changelog
- v4.0.0:
- Added
finalize()
method for branches; - Added
when()
andwhenever()
; - Added
Leaf.defaultSelector
; - Renamed
subscribe()
toobserve()
for leaves; - Renamed
unsubscribe()
tounobserve()
for leaves; - Renamed
createScheduler()
tocreateAsyncScheduler()
; - Renamed
scheduleBranch()
toschedule()
for schedulers; - Renamed
unscheduleBranch()
tounschedule()
for schedulers; - Changed
Scheduler
to be an interface and a class; - Changed the type of
selector
for leaves; - Changed
subject()
tosubject
for leaves; - Changed the implementation of
createSignal()
.
- Added
- v3.0.1 - v3.0.3:
- Minor fixes.
- v3.0.0:
- Added
clean()
for twigs; - Added
connect()
for twigs; - Added
connect()
for branches; - Added
stopped
for branches; - Added
disposed
for branches; - Added
collectSignals()
; - Added
Scheduler.async
and Scheduler.sync; - Added
reactive()
andcomputed()
; - Renamed
addTeardown()
toteardown()
; - Removed
setInterval()
andsetTimeout()
for branches.
- Added
- v2.2.0:
- Added
write()
for twigs.
- Added
- v2.0.1 - v2.0.3:
- Minor changes.
- v2.0.0:
- Added class
Scheduler
andSignal
; - Added
selector
for leaves; - Renamed
remove()
todispose()
; - Changed signature of
createBranch()
.
- Added class
License
MIT