A reactive value library supporting memoization and per-value selection of lazy or eager, chain-less dependency evaluation.
Reactive values in this implementation work similarly to spreadsheet cells. Each reactive value can either:
- Hold a direct value
- Contain a definition (formula) that calculates its value based on other reactive values
When a reactive value changes (through direct assignment or recalculation of its definition), any other reactive values that depend on it are automatically updated. This creates a dependency graph where changes propagate automatically.
By default, recalculations are performed lazily (only when the value is accessed). However, you can enable eager evaluation for reactives that need to trigger side effects immediately when their dependencies change.
const r = reactive({ options })Options:
compare: Comparison function to determine if value changed. Default:(old, new) => old !== newdef: Definition functionold => newor another reactive value to trackeager: If truthy, enables eager recalculation (default: undefined for lazy)v: Initial value (default: undefined)
- Lazy (default): Values are only recalculated when accessed through
.rv,.wv, or.getter() - Eager: Values are recalculated immediately when dependencies change
.rv- Read the current value (read-only).wv- Read/write the current value (assignment clears any definition).getter()- Get a function that returns the current value.setter()- Get a function that sets a new value
Definitions are functions that compute a reactive's value based on other values:
// Direct definition
r.def = oldValue => newValue;
// Track another reactive
r.def = otherReactive; // Shorthand for r.def = otherReactive.getterExecute changes as a batch, deferring updates until completion.
- Returns: Callback return value
Get the final non-reactive value.
bundle: If true, returns_bundle()value for bundles- Returns: The unwrapped value
Get the reactive type of a value.
- Returns:
reactive.type,reactiveBundle.type, orundefined
Execute callback without tracking dependencies.
- Returns: Callback return value
Used to wait for reactive recalculations to settle.
- Returns: A promise that resolves when reactive recalculations have settled.
rv- Read current value (computed if defined)wv- Read/write current value (clears definition on write)getter- Returns function to get current valuesetter- Returns function to set new valueaccessors- Returns[getter, setter]pair
def- Get/set the definition functioneager- Get/set eager evaluation modecompare- Get the comparison function
readonly- False for main reactive, true for read-only viewreadonlyView- Get a read-only view of this reactive$reactive- The reactive type (reactive.type)
set(value | oldValue => newValue)- Set value (chainable)setDef(def)- Set definition (chainable)setEager(eager)- Set eager mode (chainable)unready()- Force recalculation on next access (chainable)
consumer(c, add = true)- Register/unregister consumerprovider(p, add = true)- Register/unregister providerripple(distance = 0)- Propagate changes through dependency graph
// Basic value tracking
const r1 = reactive({ v: 1 });
r1.rv; // 1
// Track another reactive
const r2 = reactive({ def: r1 }); // r2.rv === r1.rv
// Compute based on another reactive
const r3 = reactive({ def: () => r1.rv }); // Also tracks r1
const r4 = reactive({ def: () => r3.rv * 2 }); // Double r3's value
// Updating values
++r1.wv; // r1.rv, r2.rv, r3.rv are now 2
r4.rv; // 4
// Breaking dependency
r3.set(5); // r3 no longer tracks r1
r4.rv; // 10 (double r3's new value)
// Using accessors
const s3 = r3.setter;
const g4 = r4.getter;
s3(v => v + 1); // Increment using setter function
g4(); // 12 (get r4's value using getter)
// Enable eager evaluation
r4.setEager(1); // Now recalculates immediately when dependencies change- Use
.rvfor read-only access and.wvfor write access to clarify intent - Make reactives with side effects "terminal nodes" (not used in other definitions)
- Use eager evaluation only when immediate side effects are needed
- Use the chainable methods (
set,setDef,setEager) for fluent configuration - Use
batch()when making multiple related changes to optimize performance