Reactive leases for data subscriptions
With sublet
you can directly attach functions to the data they care about.
You marry a function with its relevant data. They become bound to one another, a "reactive" pair.
When the data changes, the function automatically gets triggered, receiving the latest snapshot of that data.
Their "reactive marriage" is also exposed to the outside world in the form of a proxied data
object.
This allows external reads and writes into data
object. Because the function is bound, it will still always be triggered, regardless of who/what caused those updates!
Whenever the binding updates, the original data
source receives the updates, too.
This means that when new reactive pairs source their data from other bindings (or pieces of them), any updates to the "child" binding propagate to the "parent" binding. In turn, both of their partner functions are triggered.
So, sublet
sort of behaves like a store, but its dispatchers are normal variable assignments!
Instead, sublet
allows you to get back to JavaScript's basic building blocks while keeping your sanity.
There are two "versions" of sublet
, accommodating different browser support targets:
Size (gzip): 194 bytes
Availability: UMD, CommonJS, ES Module
Requires:Proxy
The more modern build of sublet
as it relies on the Proxy
class.
Because of this, the "modern" version of sublet
can work with Objects and Arrays, neither of which require the input
to have a predefined shape. This means that your Objects can receive new keys at any point, or your Arrays can mutate in any which way, and your callback
subscriber will always be made aware of those changes.
Size (gzip): 263 bytes
Availability: UMD, ES Module
Requires:Array.isArray
,Object.defineProperty
The "legacy" version is bound by Object.defineProperty
's limitations:
- the
input
must be an Object (no Arrays); - the
input
Object must have its keys defined ahead of time, beforesublet
instantiation.
Unlike the Proxy
-based solution, any new, dynamically added keys are unknown and uninitialized, which means they cannot and will never be able to trigger the callback
when they show up or change.
$ npm install --save sublet
Also available on unpkg.com:
<!-- Mode: "proxy" -->
<script src="https://unpkg.com/sublet"></script>
<!-- Mode: "legacy" -->
<script src="https://unpkg.com/sublet/legacy"></script>
// Mode: "proxy"
import sublet from 'sublet';
// Mode: "legacy"
import sublet from 'sublet/legacy';
const user = {}; // <~ "proxy" can be lazy
const user = { firstName:null, lastName:null }; // <~ "legacy" is explicit
const view = sublet(user, state => {
if (state.firstName) {
console.log(`Hello, ${state.firstName} ${state.lastName}`);
} else {
console.log('Howdy stranger~!');
}
});
//=> "Howdy stranger~!"
view.firstName = 'Nicolas';
view.lastName = 'Cage';
//=> "Hello, Nicolas Cage"
Returns: T<input>
A wrapped (aka, proxied) form of your input
is returned.
This is the reactive interface and should be used to update state.
This interface is also passed to callback
as its only argument.
Any updates through this T
interface will propagate new value(s) to the original input
, too.
Similarly, all read operations pull from the current input
object. This means that any updates to input
directly (outside of the T
interface) will not trigger the callback
subscription; however, those values will appear when read through T
and/or in the next callback
invocation.
Note: In "proxy" mode, the
T
is aProxy
instance and in "legacy" mode, it is anObject
.
However, these are functionally equivalent since aProxy
can't be detected anwyay.
Type: Object
The original state data to observe.
Important: Modes operate differently!
Mode: "proxy"
Array
types are permitted- When an
Object
, the keys do not need to be declared upfrontMode: "legacy"
Array
types are not permitted
The originalinput
is immediately returned, withoutcallback
attachment.- The
Object
must have predefined keys!
Reactivity can only be established for known keys.
You must define your default or empty state.
Type: Function
The callback to run whenever the paired reactive data updates.
This function receives the reactive data (T<input>
) as its only argument.
The callback will run immediately when setting up a sublet
instance.
This means that your callback should be capable of handling "setup" vs "update" usage. Alternatively, you can separate those actions and attach the "update" function as your sublet
subscriber.
After initializing, the callback
will only be queued to run if an updated value was not strictly equal to its previous value.
Similarly, any updates to the T<input>
argument will enqueue a new update cycle.
Finally, the callback
is debounced. This means that multiple T<input>
updates will re-run the callback
once.
import sublet from 'sublet';
let num = 0;
const view = sublet({}, () => console.log(`Render #${++num}`));
//=> Render #1
view.foo = 123;
// Render #2
await sleep(10); //~> 10ms later
view.foo = 123;
// (no render, value identical)
await sleep(10); //~> 10ms later
view.foo = 1;
view.bar = 2;
view.baz = 3;
//=> Render #3
The "legacy" version works just about everywhere.
The "proxy" version works anywhere that was considered "modern" in the last 4 years.
Chrome | Safari | Firefox | Edge | Opera | IE | Node.js | |
---|---|---|---|---|---|---|---|
"proxy" | 49 | 10 | 18 | 12 | 36 | ❌ | 6.0.0 |
"legacy" | 5 | 5.1 | 4 | 12 | 11.6 | 9 | ✅ |
There are 101 observable/reactive libraries and patterns out there – it would be impossible to name them all.
However, the reactivity of Svelte 3.x, specifically, inspired me.
MIT © Luke Edwards