bruh
Version:
The thinnest possible layer between development and production for the modern web.
161 lines (127 loc) • 4.53 kB
JavaScript
export const isReactive = Symbol.for("bruh reactive")
// A super simple and performant reactive value implementation
export class SimpleReactive {
[] = true
constructor(value) {
this.
}
get value() {
return this.
}
set value(newValue) {
if (newValue === this.
return
this.
for (const reaction of this.
reaction()
}
addReaction(reaction) {
this.
return () =>
this.
}
}
// A reactive implementation for building functional reactive graphs
// Ensures state consistency, minimal node updates, and transparent update batching
export class FunctionalReactive {
[] = true
// For derived nodes, f is the derivation function
// Source nodes are 0 deep in the derivation graph
// This is for topological sort
// All nodes have a set of derivatives that update when the node changes
// Keep track of all the pending changes from the value setter
static
// A queue of derivatives to potentially update, sorted into sets by depth
// This starts with depth 1 and can potentially have holes
static
// A queue of reactions to run after the graph is fully updated
static
constructor(x, f) {
if (!f) {
this.
return
}
this.
this.
this.
x.forEach(d => d.
}
get value() {
// If there are any pending updates
if (FunctionalReactive.
// Heuristic quick invalidation for derived nodes
// Apply updates now, it's ok that there's already a microtask queued for this
if (this.
FunctionalReactive.applyUpdates()
// If this is a source node that was updated, just return that
// new value without actually updating any derived nodes yet
else if (FunctionalReactive.
return FunctionalReactive.
}
return this.
}
set value(newValue) {
// Only allow source nodes to be directly updated
if (this.
return
// Unless asked for earlier, these updates are just queued up until the microtasks run
if (!FunctionalReactive.
queueMicrotask(FunctionalReactive.applyUpdates)
FunctionalReactive.
}
addReaction(reaction) {
this.
return () =>
this.
}
// Apply an update for a node and queue its derivatives if it actually changed
if (newValue === this.
return
this.
FunctionalReactive.
const queue = FunctionalReactive.
for (const derivative of this.
const depth = derivative.
if (!queue[depth])
queue[depth] = new Set()
queue[depth].add(derivative)
}
}
// Apply pending updates from actually changed source nodes
static applyUpdates() {
if (!FunctionalReactive.
return
// Bootstrap by applying the updates from the pending setters
for (const [sourceNode, newValue] of FunctionalReactive.
sourceNode.
FunctionalReactive.
// Iterate down the depths, ignoring holes
// Note that both the queue (Array) and each depth Set iterators update as items are added
for (const depthSet of FunctionalReactive.
for (const derivative of depthSet)
derivative.
FunctionalReactive.
// Call all reactions now that the graph has a fully consistent state
for (const reaction of FunctionalReactive.
reaction()
FunctionalReactive.
}
}
// A little convenience function
export const r = (x, f) => new FunctionalReactive(x, f)
// Do something with a value, updating if it is reactive
export const reactiveDo = (x, f) => {
if (x?.[isReactive]) {
f(x.value)
return x.addReaction(() => f(x.value))
}
f(x)
}