Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Created June 18, 2024 17:40
Show Gist options
  • Save thomaswilburn/28baa0a56626c6adfc3fb45aaa2072dc to your computer and use it in GitHub Desktop.
Save thomaswilburn/28baa0a56626c6adfc3fb45aaa2072dc to your computer and use it in GitHub Desktop.
Preact Signals But With EventTarget
var stack = [];
var verbose = false;
// make our own undefined sentinel value
var undefined = Symbol("undefined signal");
class Signal extends EventTarget {
#value = undefined;
#computation = null;
#lifetime = new AbortController();
constructor(input) {
super();
this.onChange = this.onChange.bind(this);
if (typeof input == "function") {
this.#computation = input;
var _ = this.value;
} else {
this.value = input;
}
}
get value() {
if (verbose) console.log(`- getting value`, this.#value == undefined ? "(invalidated)" : this.#value);
var top = stack[stack.length - 1];
if (top) {
var { onChange, signal } = top;
this.addEventListener("signal", onChange, { once: true, signal });
}
if (this.#computation && this.#value == undefined) {
stack.push(this);
if (verbose) console.log(`- recomputing derived value, stack depth: ${stack.length}`);
this.#value = this.#computation();
stack.pop();
if (verbose) console.log(`- result: ${this.#value}, stack depth: ${stack.length}`);
}
if (this.#value != undefined) {
return this.#value;
}
}
set value(value) {
if (verbose) console.log(`- setting value`, value);
this.#value = value;
this.dispatchEvent(new Event("signal"));
}
onChange(e) {
if (verbose) console.log(`- value invalidated`);
this.#value = undefined;
this.dispatchEvent(new Event("signal"));
}
get signal() {
return this.#lifetime.signal;
}
destroy() {
if (verbose) console.log(`- value destroyed`, this.#value);
this.#lifetime.abort();
}
}
// var a = new Signal(1);
// var b = new Signal(2);
// var yes = new Signal(true);
// console.log("initial values", a.value, b.value);
// var derived = new Signal(() => {
// if (yes.value) {
// return a.value + b.value;
// } else {
// return a.value;
// }
// });
// console.log("initial derived", derived.value);
// a.value = 10;
// console.log("altered A", derived.value);
// yes.value = false;
// a.value = 1;
// console.log("altered conditional", derived.value);
// b.value = 2;
// console.log("altered unsubscribed?", derived.value);
// b.value = 2;
// yes.value = true;
// console.log("conditional reverted", derived.value);
// derived.destroy();
// console.log("destroyed computed value", derived.value);
// a.value = 10;
// console.log("No changes should be observed");
console.log("--- SIGNAL DEPENDENCY TRACKING ---");
const choice = new Signal(true);
const funk = new Signal("uptown");
const purple = new Signal("haze");
const c = new Signal(() => {
if (choice.value) {
console.log(funk.value, "funk");
} else {
console.log("purple", purple.value);
}
});
c.value;
console.log("setting purple rain");
purple.value = "rain";
c.value;
console.log("setting conditional");
choice.value = false;
c.value;
console.log("setting funk");
funk.value = "da";
c.value;
console.log("setting again");
funk.value = "da";
c.value;
console.log("--- NESTED EFFECTS ---");
const count = new Signal(1);
const double = new Signal(() => count.value * 2);
const quadruple = new Signal(() => double.value * 2);
const octuple = new Signal(() => quadruple.value * 2);
count.value = 1;
var effect = new Signal(() => {
console.log("octuple is now", octuple.value);
}); // Console: octuple value is now 8
effect.destroy();
count.value = 20; // nothing gets printed to the console
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment