Reactivity API to wire any reactive libs and native states together. It allows to mix different reactive libs, use async functions with automatic dependency tracking, observe any states in same way, make native DOM reactive etc.
- Most of modern apps based on reactive paradigm.
- Popular and promising approach is pull-reactivity.
- Pull-reactivity achieves automatic dependency trackig, automatic data-flow optimization and resources lifetime control. See the analysis of reactivity approaches.
- Famed pull-reactivity libs: MobX, VueJS, $mol.
- All implementations have to use global variable to connect observers with observables.
- Every implementations has different global variables with different API. Modules based on different libs can't work together directly.
- Observing is required for reactivity, but native API's isn't reactive. Some native values can be observed through different API (
onhashchange
forlocation
,MutationObserver
forDOM
etc), but some can't (sessionStorage, window.title etc) - Tracking through global variable works incorrectly with async functions. MobX have to use generators instead.
- All native values can be observed through the same API.
- Any statefull lib can be easely adopted for observing with minimal impact on it's size.
- Fast enablinng/disabling autowire.
- Minimal impact on performance/memory when autowire is disabled.
- Async functions support.
await
andyield
stores current subscriber before call and restores after. - DOM reactivity support. When browsers uses element to render docuemnt it uses autowire to subscribeto any publishers including other DOM elements.
- Cyclic subscriptions throws
Wire.CyclicError
error on subscribe.
declare namespace Wire {
/**
* Current subscriber that auto wire with all touching publishers.
*/
export let auto: PubSub | null
/**
* Reactive publisher with minimal memory cost.
*/
export class Pub {
/**
* List of all subscribed subscribers.
* Can be observed through subscriber too.
*/
subs: PubSub[]
/**
* Notify all subscribed subscribers about changes related to this publisher.
*/
emit(): void
}
/**
* Reactive subscriber + publisher (republisher).
*/
export class PubSub extends Pub {
/**
* List of all connected publishers.
* Can be observed through Sub too.
*/
pubs: Pub[]
/**
* Begin auto wire to publishers.
*/
begin(): void
/**
* Promotes next publisher to auto wire its together.
* Can be executed only between `begin` and `end`.
*/
promo( pub: Pub ): void
/**
* Returns next already auto wired publisher. It can be easely repormoted.
* Can be executed only between `begin` and `end`.
*/
next(): Pub | null
/**
* Ends auto wire to publishers and unsubscribes from unpromoted publishers.
* Executed automatically when cuurent fuction ends.
*/
end(): void
/**
* Called when some publisher emits.
* Can be overrided with custom logic.
* Reemits to self subscribers by default.
*/
absorb( pub?: Pub ): void
/**
* Unsubscribes from all publishers then emits to subscribers.
*/
destructor(): void
}
}
/**
* Simple class with reactivity support.
*/
class ObservableUser {
#name_pub = new Wire.Pub
#name = 'Anonymous'
get name() {
Wire.auto?.promo( this.#name_pub )
return this.#name
}
set name( next: string ) {
this.#name = next
this.#name_pub.emit()
}
}
/**
* Simple reactive observer.
*/
class ReactiveLogger extends Wire.PubSub {
#pull: ()=> unknown
constructor(
pull: ()=> unknown
) {
super()
this.#pull = pull
}
absorb() {
requestAnimationFrame( ()=> this.#pull() )
}
pull() {
this.begin()
try {
console.log( this.#pull() )
} finally {
this.end()
}
this.emit()
}
}
async function test() {
document.title = 'Hello'
const alice = new ObservableUser
const logger = new ReactiveLogger( ()=> document.title + alice.name )
// initial pull and auto wire with publishers
logger.pull()
// change reactive state
alice.name = 'Alice'
document.title = 'Bye'
// unsubscribe logger
logger.destructor()
// Prints "HelloAnonymous", then "HelloAlice", then "ByeAlice" in the next frame
}
<input id="input" />
<p id="output"></p>
const input = document.getElementById('input')
const output = document.getElementById('output')
Object.defineProperty( output, 'innerText', {
get: ()=> 'Hello ' + input.value
} )
- $mol_wire - TS lib with approach like proposed.